]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
New upstream version 49.5
authorSimon McVittie <smcv@debian.org>
Tue, 17 Mar 2026 10:34:11 +0000 (10:34 +0000)
committerSimon McVittie <smcv@debian.org>
Tue, 17 Mar 2026 10:34:11 +0000 (10:34 +0000)
1  2 
subprojects/gvc/gvc-mixer-control.c

index 45764849bb2e0c2ffbc6ecbc10b587bd84d72516,0000000000000000000000000000000000000000..57dc2d4f2ccde177fc7e935f19c10029e2db7a18
mode 100644,000000..100644
--- /dev/null
@@@ -1,3923 -1,0 +1,3924 @@@
-                            GvcMixerStream     *stream)
 +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
 + *
 + * Copyright (C) 2006-2008 Lennart Poettering
 + * Copyright (C) 2008 Sjoerd Simons <sjoerd@luon.net>
 + * Copyright (C) 2008 William Jon McCann
 + * Copyright (C) 2012 Conor Curran
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License
 + * along with this program; if not, write to the Free Software
 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 + *
 + */
 +
 +#include "config.h"
 +
 +#include <stdlib.h>
 +#include <stdio.h>
 +#include <unistd.h>
 +
 +#include <glib.h>
 +#include <glib/gi18n-lib.h>
 +
 +#include <pulse/pulseaudio.h>
 +#include <pulse/glib-mainloop.h>
 +#include <pulse/ext-stream-restore.h>
 +
 +#ifdef HAVE_ALSA
 +#include <alsa/asoundlib.h>
 +#endif /* HAVE_ALSA */
 +
 +#include "gvc-mixer-control.h"
 +#include "gvc-mixer-sink.h"
 +#include "gvc-mixer-source.h"
 +#include "gvc-mixer-sink-input.h"
 +#include "gvc-mixer-source-output.h"
 +#include "gvc-mixer-event-role.h"
 +#include "gvc-mixer-card.h"
 +#include "gvc-mixer-card-private.h"
 +#include "gvc-channel-map-private.h"
 +#include "gvc-mixer-control-private.h"
 +#include "gvc-mixer-ui-device.h"
 +
 +#define RECONNECT_DELAY 5
 +
 +enum {
 +        PROP_0,
 +        PROP_NAME,
 +        N_PROPS
 +};
 +static GParamSpec *obj_props[N_PROPS] = { NULL, };
 +
 +struct GvcMixerControlPrivate
 +{
 +        pa_glib_mainloop *pa_mainloop;
 +        pa_mainloop_api  *pa_api;
 +        pa_context       *pa_context;
 +        guint             server_protocol_version;
 +        int               n_outstanding;
 +        guint             reconnect_id;
 +        char             *name;
 +
 +        gboolean          default_sink_is_set;
 +        guint             default_sink_id;
 +        char             *default_sink_name;
 +        gboolean          default_source_is_set;
 +        guint             default_source_id;
 +        char             *default_source_name;
 +
 +        gboolean          event_sink_input_is_set;
 +        guint             event_sink_input_id;
 +
 +        GHashTable       *all_streams;
 +        GHashTable       *sinks; /* fixed outputs */
 +        GHashTable       *sources; /* fixed inputs */
 +        GHashTable       *sink_inputs; /* routable output streams */
 +        GHashTable       *source_outputs; /* routable input streams */
 +        GHashTable       *clients;
 +        GHashTable       *cards;
 +
 +        GvcMixerStream   *new_default_sink_stream; /* new default sink stream, used in gvc_mixer_control_set_default_sink () */
 +        GvcMixerStream   *new_default_source_stream; /* new default source stream, used in gvc_mixer_control_set_default_source () */
 +
 +        GHashTable       *ui_outputs; /* UI visible outputs */
 +        GHashTable       *ui_inputs;  /* UI visible inputs */
 +
 +        /* When we change profile on a device that is not the server default sink,
 +         * it will jump back to the default sink set by the server to prevent the
 +         * audio setup from being 'outputless'.
 +         *
 +         * All well and good but then when we get the new stream created for the
 +         * new profile how do we know that this is the intended default or selected
 +         * device the user wishes to use. */
 +        guint            profile_swapping_device_id;
 +
 +#ifdef HAVE_ALSA
 +        int      headset_card;
 +        gboolean has_headsetmic;
 +        gboolean has_headphonemic;
 +        gboolean headset_plugged_in;
 +        char    *headphones_name;
 +        char    *headsetmic_name;
 +        char    *headphonemic_name;
 +        char    *internalspk_name;
 +        char    *internalmic_name;
 +#endif /* HAVE_ALSA */
 +
 +        GvcMixerControlState state;
 +};
 +
 +enum {
 +        STATE_CHANGED,
 +        STREAM_ADDED,
 +        STREAM_REMOVED,
 +        STREAM_CHANGED,
 +        CARD_ADDED,
 +        CARD_REMOVED,
 +        DEFAULT_SINK_CHANGED,
 +        DEFAULT_SOURCE_CHANGED,
 +        ACTIVE_OUTPUT_UPDATE,
 +        ACTIVE_INPUT_UPDATE,
 +        OUTPUT_ADDED,
 +        INPUT_ADDED,
 +        OUTPUT_REMOVED,
 +        INPUT_REMOVED,
 +        AUDIO_DEVICE_SELECTION_NEEDED,
 +        LAST_SIGNAL
 +};
 +
 +static guint signals [LAST_SIGNAL] = { 0, };
 +
 +static void     gvc_mixer_control_finalize   (GObject              *object);
 +
 +G_DEFINE_TYPE_WITH_PRIVATE (GvcMixerControl, gvc_mixer_control, G_TYPE_OBJECT)
 +
 +pa_context *
 +gvc_mixer_control_get_pa_context (GvcMixerControl *control)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +        return control->priv->pa_context;
 +}
 +
 +/**
 + * gvc_mixer_control_get_event_sink_input:
 + * @control:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerStream *
 +gvc_mixer_control_get_event_sink_input (GvcMixerControl *control)
 +{
 +        GvcMixerStream *stream;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        stream = g_hash_table_lookup (control->priv->all_streams,
 +                                      GUINT_TO_POINTER (control->priv->event_sink_input_id));
 +
 +        return stream;
 +}
 +
 +static void
 +gvc_mixer_control_stream_restore_cb (pa_context *c,
 +                                   GvcMixerStream *new_stream,
 +                                     const pa_ext_stream_restore_info *info,
 +                                     GvcMixerControl *control)
 +{
 +        pa_operation *o;
 +        pa_ext_stream_restore_info new_info;
 +
 +        if (new_stream == NULL)
 +                return;
 +
 +        new_info.name = info->name;
 +        new_info.channel_map = info->channel_map;
 +        new_info.volume = info->volume;
 +        new_info.mute = info->mute;
 +
 +        new_info.device = gvc_mixer_stream_get_name (new_stream);
 +
 +        o = pa_ext_stream_restore_write (control->priv->pa_context,
 +                                         PA_UPDATE_REPLACE,
 +                                         &new_info, 1,
 +                                         TRUE, NULL, NULL);
 +
 +        if (o == NULL) {
 +                g_warning ("pa_ext_stream_restore_write() failed: %s",
 +                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 +                return;
 +        }
 +
 +        g_debug ("Changed default device for %s to %s", info->name, new_info.device);
 +
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +gvc_mixer_control_stream_restore_sink_cb (pa_context *c,
 +                                          const pa_ext_stream_restore_info *info,
 +                                          int eol,
 +                                          void *userdata)
 +{
 +        GvcMixerControl *control = (GvcMixerControl *) userdata;
 +        if (eol || info == NULL || !g_str_has_prefix(info->name, "sink-input-by"))
 +                return;
 +        gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_sink_stream, info, control);
 +}
 +
 +static void
 +gvc_mixer_control_stream_restore_source_cb (pa_context *c,
 +                                            const pa_ext_stream_restore_info *info,
 +                                            int eol,
 +                                            void *userdata)
 +{
 +        GvcMixerControl *control = (GvcMixerControl *) userdata;
 +        if (eol || info == NULL || !g_str_has_prefix(info->name, "source-output-by"))
 +                return;
 +        gvc_mixer_control_stream_restore_cb (c, control->priv->new_default_source_stream, info, control);
 +}
 +
 +/**
 + * gvc_mixer_control_lookup_device_from_stream:
 + * @control:
 + * @stream:
 + *
 + * Returns: (transfer none): a #GvcUIDevice or %NULL
 + */
 +GvcMixerUIDevice *
 +gvc_mixer_control_lookup_device_from_stream (GvcMixerControl *control,
 +                                             GvcMixerStream *stream)
 +{
 +        GList                   *devices, *d;
 +        const GList             *ports;
 +        GvcMixerUIDevice        *ret;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), NULL);
 +
 +        if (GVC_IS_MIXER_SOURCE (stream))
 +               devices = g_hash_table_get_values (control->priv->ui_inputs);
 +        else
 +               devices = g_hash_table_get_values (control->priv->ui_outputs);
 +
 +        ret = NULL;
 +        ports = gvc_mixer_stream_get_ports (stream);
 +
 +        for (d = devices; d != NULL; d = d->next) {
 +                GvcMixerUIDevice *device = d->data;
 +                guint stream_id = G_MAXUINT;
 +                const GvcMixerStreamPort *port;
 +
 +                g_object_get (G_OBJECT (device),
 +                             "stream-id", &stream_id,
 +                              NULL);
 +
 +                if (stream_id != gvc_mixer_stream_get_id (stream))
 +                        continue;
 +
 +                if (ports == NULL) {
 +                        g_debug ("lookup-device-from-stream - %s - is portless ",
 +                                 gvc_mixer_ui_device_get_description (device));
 +                        ret = device;
 +                        break;
 +                }
 +
 +                port = gvc_mixer_stream_get_port (stream);
 +                if (g_strcmp0 (gvc_mixer_ui_device_get_port (device), port->port) == 0) {
 +                        g_debug ("lookup-device-from-stream found device: device description '%s', device port = '%s', device stream id %i AND stream port = '%s' stream id '%u' and stream description '%s'",
 +                                 gvc_mixer_ui_device_get_description (device),
 +                                 gvc_mixer_ui_device_get_port (device),
 +                                 stream_id,
 +                                 port->port,
 +                                 gvc_mixer_stream_get_id (stream),
 +                                 gvc_mixer_stream_get_description (stream));
 +                        ret = device;
 +                        break;
 +
 +                }
 +        }
 +
 +        if (!ret)
 +                g_debug ("lookup-device-from-stream - Could not find a device for stream '%s'", gvc_mixer_stream_get_description (stream));
 +
 +        g_list_free (devices);
 +
 +        return ret;
 +}
 +
 +gboolean
 +gvc_mixer_control_set_default_sink (GvcMixerControl *control,
 +                                    GvcMixerStream  *stream)
 +{
 +        pa_operation *o;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 +        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
 +
 +        g_debug ("about to set default sink on server");
 +        o = pa_context_set_default_sink (control->priv->pa_context,
 +                                         gvc_mixer_stream_get_name (stream),
 +                                         NULL,
 +                                         NULL);
 +        if (o == NULL) {
 +                g_warning ("pa_context_set_default_sink() failed: %s",
 +                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 +                return FALSE;
 +        }
 +
 +        pa_operation_unref (o);
 +
 +        control->priv->new_default_sink_stream = stream;
 +        g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_sink_stream);
 +
 +        o = pa_ext_stream_restore_read (control->priv->pa_context,
 +                                        gvc_mixer_control_stream_restore_sink_cb,
 +                                        control);
 +
 +        if (o == NULL) {
 +                g_warning ("pa_ext_stream_restore_read() failed: %s",
 +                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 +                return FALSE;
 +        }
 +
 +        pa_operation_unref (o);
 +
 +        return TRUE;
 +}
 +
 +gboolean
 +gvc_mixer_control_set_default_source (GvcMixerControl *control,
 +                                      GvcMixerStream  *stream)
 +{
 +        pa_operation *o;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 +        g_return_val_if_fail (GVC_IS_MIXER_STREAM (stream), FALSE);
 +
 +        o = pa_context_set_default_source (control->priv->pa_context,
 +                                           gvc_mixer_stream_get_name (stream),
 +                                           NULL,
 +                                           NULL);
 +        if (o == NULL) {
 +                g_warning ("pa_context_set_default_source() failed");
 +                return FALSE;
 +        }
 +
 +        pa_operation_unref (o);
 +
 +        control->priv->new_default_source_stream = stream;
 +        g_object_add_weak_pointer (G_OBJECT (stream), (gpointer *) &control->priv->new_default_source_stream);
 +
 +        o = pa_ext_stream_restore_read (control->priv->pa_context,
 +                                        gvc_mixer_control_stream_restore_source_cb,
 +                                        control);
 +
 +        if (o == NULL) {
 +                g_warning ("pa_ext_stream_restore_read() failed: %s",
 +                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 +                return FALSE;
 +        }
 +
 +        pa_operation_unref (o);
 +
 +        return TRUE;
 +}
 +
 +/**
 + * gvc_mixer_control_get_default_sink:
 + * @control:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerStream *
 +gvc_mixer_control_get_default_sink (GvcMixerControl *control)
 +{
 +        GvcMixerStream *stream;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        if (control->priv->default_sink_is_set) {
 +                stream = g_hash_table_lookup (control->priv->all_streams,
 +                                              GUINT_TO_POINTER (control->priv->default_sink_id));
 +        } else {
 +                stream = NULL;
 +        }
 +
 +        return stream;
 +}
 +
 +/**
 + * gvc_mixer_control_get_default_source:
 + * @control:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerStream *
 +gvc_mixer_control_get_default_source (GvcMixerControl *control)
 +{
 +        GvcMixerStream *stream;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        if (control->priv->default_source_is_set) {
 +                stream = g_hash_table_lookup (control->priv->all_streams,
 +                                              GUINT_TO_POINTER (control->priv->default_source_id));
 +        } else {
 +                stream = NULL;
 +        }
 +
 +        return stream;
 +}
 +
 +static gpointer
 +gvc_mixer_control_lookup_id (GHashTable *hash_table,
 +                             guint       id)
 +{
 +        return g_hash_table_lookup (hash_table,
 +                                    GUINT_TO_POINTER (id));
 +}
 +
 +/**
 + * gvc_mixer_control_lookup_stream_id:
 + * @control:
 + * @id:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerStream *
 +gvc_mixer_control_lookup_stream_id (GvcMixerControl *control,
 +                                    guint            id)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        return gvc_mixer_control_lookup_id (control->priv->all_streams, id);
 +}
 +
 +/**
 + * gvc_mixer_control_lookup_card_id:
 + * @control:
 + * @id:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerCard *
 +gvc_mixer_control_lookup_card_id (GvcMixerControl *control,
 +                                  guint            id)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        return gvc_mixer_control_lookup_id (control->priv->cards, id);
 +}
 +
 +/**
 + * gvc_mixer_control_lookup_output_id:
 + * @control:
 + * @id:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerUIDevice *
 +gvc_mixer_control_lookup_output_id (GvcMixerControl *control,
 +                                    guint            id)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        return gvc_mixer_control_lookup_id (control->priv->ui_outputs, id);
 +}
 +
 +/**
 + * gvc_mixer_control_lookup_input_id:
 + * @control:
 + * @id:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerUIDevice *
 +gvc_mixer_control_lookup_input_id (GvcMixerControl *control,
 +                                    guint            id)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        return gvc_mixer_control_lookup_id (control->priv->ui_inputs, id);
 +}
 +
 +/**
 + * gvc_mixer_control_get_stream_from_device:
 + * @control:
 + * @device:
 + *
 + * Returns: (transfer none):
 + */
 +GvcMixerStream *
 +gvc_mixer_control_get_stream_from_device (GvcMixerControl *control,
 +                                          GvcMixerUIDevice *device)
 +{
 +        gint stream_id;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +        g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), NULL);
 +
 +        stream_id = gvc_mixer_ui_device_get_stream_id (device);
 +
 +        if (stream_id == GVC_MIXER_UI_DEVICE_INVALID) {
 +                g_debug ("gvc_mixer_control_get_stream_from_device - device has a null stream");
 +                return NULL;
 +        }
 +        return gvc_mixer_control_lookup_stream_id (control, stream_id);
 +}
 +
 +/**
 + * gvc_mixer_control_change_profile_on_selected_device:
 + * @control:
 + * @device:
 + * @profile: (allow-none): Can be %NULL if any profile present on this port is okay
 + *
 + * Returns: This method will attempt to swap the profile on the card of
 + * the device with given profile name.  If successful it will set the
 + * preferred profile on that device so as we know the next time the user
 + * moves to that device it should have this profile active.
 + */
 +gboolean
 +gvc_mixer_control_change_profile_on_selected_device (GvcMixerControl  *control,
 +                                                     GvcMixerUIDevice *device,
 +                                                     const gchar      *profile)
 +{
 +        const gchar         *best_profile;
 +        GvcMixerCardProfile *current_profile;
 +        GvcMixerCard        *card;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 +        g_return_val_if_fail (GVC_IS_MIXER_UI_DEVICE (device), FALSE);
 +
 +        g_object_get (G_OBJECT (device), "card", &card, NULL);
 +        current_profile = gvc_mixer_card_get_profile (card);
 +
 +        if (current_profile)
 +                best_profile = gvc_mixer_ui_device_get_best_profile (device, profile, current_profile->profile);
 +        else
 +                best_profile = profile;
 +
 +        g_assert (best_profile);
 +
 +        g_debug ("Selected '%s', moving to profile '%s' on card '%s' on stream id %i",
 +                profile ? profile : "(any)", best_profile,
 +                gvc_mixer_card_get_name (card),
 +                gvc_mixer_ui_device_get_stream_id (device));
 +
 +        g_debug ("default sink name = %s and default sink id %u",
 +                 control->priv->default_sink_name,
 +                 control->priv->default_sink_id);
 +
 +        control->priv->profile_swapping_device_id = gvc_mixer_ui_device_get_id (device);
 +
 +        if (gvc_mixer_card_change_profile (card, best_profile)) {
 +                gvc_mixer_ui_device_set_user_preferred_profile (device, best_profile);
 +                return TRUE;
 +        }
 +        return FALSE;
 +}
 +
 +/**
 + * gvc_mixer_control_change_output:
 + * @control:
 + * @output:
 + * This method is called from the UI when the user selects a previously unselected device.
 + * - Firstly it queries the stream from the device.
 + *   - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
 + *   In the scenario of a NULL stream on the device
 + *        - It fetches the device's preferred profile or if NULL the profile with the highest priority on that device.
 + *        - It then caches this device in control->priv->cached_desired_output_id so that when the update_sink triggered
 + *          from when we attempt to change profile we will know exactly what device to highlight on that stream.
 + *        - It attempts to swap the profile on the card from that device and returns.
 + * - Next, it handles network or bluetooth streams that only require their stream to be made the default.
 + * - Next it deals with port changes so if the stream's active port is not the same as the port on the device
 + *   it will attempt to change the port on that stream to be same as the device. If this fails it will return.
 + * - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active output device.
 + */
 +void
 +gvc_mixer_control_change_output (GvcMixerControl *control,
 +                                 GvcMixerUIDevice* output)
 +{
 +        GvcMixerStream           *stream;
 +        GvcMixerStream           *default_stream;
 +        const GvcMixerStreamPort *active_port;
 +        const gchar              *output_port;
 +
 +        g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
 +        g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (output));
 +
 +        g_debug ("control change output");
 +
 +        stream = gvc_mixer_control_get_stream_from_device (control, output);
 +        if (stream == NULL) {
 +                gvc_mixer_control_change_profile_on_selected_device (control,
 +                        output, NULL);
 +                return;
 +        }
 +
 +        if (!gvc_mixer_ui_device_has_ports (output)) {
 +                g_debug ("Did we try to move to a software/bluetooth sink ?");
 +                if (!gvc_mixer_control_set_default_sink (control, stream)) {
 +                        g_warning ("Failed to set default sink with stream from output %s",
 +                                   gvc_mixer_ui_device_get_description (output));
 +                }
 +                return;
 +        }
 +
 +        active_port = gvc_mixer_stream_get_port (stream);
 +        output_port = gvc_mixer_ui_device_get_port (output);
 +        /* First ensure the correct port is active on the sink */
 +        if (g_strcmp0 (active_port->port, output_port) != 0) {
 +                g_debug ("Port change, switch to = %s", output_port);
 +                if (gvc_mixer_stream_change_port (stream, output_port) == FALSE) {
 +                        g_warning ("Could not change port !");
 +                        return;
 +                }
 +        }
 +
 +        default_stream = gvc_mixer_control_get_default_sink (control);
 +
 +        /* Finally if we are not on the correct stream, swap over. */
 +        if (stream != default_stream) {
 +                g_debug ("Attempting to swap over to stream %s ",
 +                         gvc_mixer_stream_get_description (stream));
 +                if (!gvc_mixer_control_set_default_sink (control, stream)) {
 +                        g_warning ("Failed to set default sink from output %s",
 +                                   gvc_mixer_ui_device_get_description (output));
 +                }
 +        }
 +}
 +
 +
 +/**
 + * gvc_mixer_control_change_input:
 + * @control:
 + * @input:
 + * This method is called from the UI when the user selects a previously unselected device.
 + * - Firstly it queries the stream from the device.
 + *   - It assumes that if the stream is null that it cannot be a bluetooth or network stream (they never show unless they have valid sinks and sources)
 + *   In the scenario of a NULL stream on the device
 + *        - It fetches the device's preferred profile or if NULL the profile with the highest priority on that device.
 + *        - It then caches this device in control->priv->cached_desired_input_id so that when the update_source triggered
 + *          from when we attempt to change profile we will know exactly what device to highlight on that stream.
 + *        - It attempts to swap the profile on the card from that device and returns.
 + * - Next, it handles network or bluetooth streams that only require their stream to be made the default.
 + * - Next it deals with port changes so if the stream's active port is not the same as the port on the device
 + *   it will attempt to change the port on that stream to be same as the device. If this fails it will return.
 + * - Finally it will set this new stream to be the default stream and emit a signal for the UI confirming the active input device.
 + */
 +void
 +gvc_mixer_control_change_input (GvcMixerControl *control,
 +                                GvcMixerUIDevice* input)
 +{
 +        GvcMixerStream           *stream;
 +        GvcMixerStream           *default_stream;
 +        const GvcMixerStreamPort *active_port;
 +        const gchar              *input_port;
 +
 +        g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
 +        g_return_if_fail (GVC_IS_MIXER_UI_DEVICE (input));
 +
 +        stream = gvc_mixer_control_get_stream_from_device (control, input);
 +        if (stream == NULL) {
 +                gvc_mixer_control_change_profile_on_selected_device (control,
 +                        input, NULL);
 +                return;
 +        }
 +
 +        if (!gvc_mixer_ui_device_has_ports (input)) {
 +                g_debug ("Did we try to move to a software/bluetooth source ?");
 +                if (! gvc_mixer_control_set_default_source (control, stream)) {
 +                        g_warning ("Failed to set default source with stream from input %s",
 +                                   gvc_mixer_ui_device_get_description (input));
 +                }
 +                return;
 +        }
 +
 +        active_port = gvc_mixer_stream_get_port (stream);
 +        input_port = gvc_mixer_ui_device_get_port (input);
 +        /* First ensure the correct port is active on the sink */
 +        if (g_strcmp0 (active_port->port, input_port) != 0) {
 +                g_debug ("Port change, switch to = %s", input_port);
 +                if (gvc_mixer_stream_change_port (stream, input_port) == FALSE) {
 +                        g_warning ("Could not change port!");
 +                        return;
 +                }
 +        }
 +
 +        default_stream = gvc_mixer_control_get_default_source (control);
 +
 +        /* Finally if we are not on the correct stream, swap over. */
 +        if (stream != default_stream) {
 +                g_debug ("change-input - attempting to swap over to stream %s",
 +                         gvc_mixer_stream_get_description (stream));
 +                if (!gvc_mixer_control_set_default_source (control, stream)) {
 +                        g_warning ("Failed to set default source from input %s",
 +                                   gvc_mixer_ui_device_get_description (input));
 +                }
 +        }
 +}
 +
 +
 +static void
 +listify_hash_values_hfunc (gpointer key,
 +                           gpointer value,
 +                           gpointer user_data)
 +{
 +        GSList **list = user_data;
 +
 +        *list = g_slist_prepend (*list, value);
 +}
 +
 +static int
 +gvc_name_collate (const char *namea,
 +                  const char *nameb)
 +{
 +        if (nameb == NULL && namea == NULL)
 +                return 0;
 +        if (nameb == NULL)
 +                return 1;
 +        if (namea == NULL)
 +                return -1;
 +
 +        return g_utf8_collate (namea, nameb);
 +}
 +
 +static int
 +gvc_card_collate (GvcMixerCard *a,
 +                  GvcMixerCard *b)
 +{
 +        const char *namea;
 +        const char *nameb;
 +
 +        g_return_val_if_fail (a == NULL || GVC_IS_MIXER_CARD (a), 0);
 +        g_return_val_if_fail (b == NULL || GVC_IS_MIXER_CARD (b), 0);
 +
 +        namea = gvc_mixer_card_get_name (a);
 +        nameb = gvc_mixer_card_get_name (b);
 +
 +        return gvc_name_collate (namea, nameb);
 +}
 +
 +/**
 + * gvc_mixer_control_get_cards:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerCard):
 + */
 +GSList *
 +gvc_mixer_control_get_cards (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->cards,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_card_collate);
 +}
 +
 +static int
 +gvc_stream_collate (GvcMixerStream *a,
 +                    GvcMixerStream *b)
 +{
 +        const char *namea;
 +        const char *nameb;
 +
 +        g_return_val_if_fail (a == NULL || GVC_IS_MIXER_STREAM (a), 0);
 +        g_return_val_if_fail (b == NULL || GVC_IS_MIXER_STREAM (b), 0);
 +
 +        namea = gvc_mixer_stream_get_name (a);
 +        nameb = gvc_mixer_stream_get_name (b);
 +
 +        return gvc_name_collate (namea, nameb);
 +}
 +
 +/**
 + * gvc_mixer_control_get_streams:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerStream):
 + */
 +GSList *
 +gvc_mixer_control_get_streams (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->all_streams,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 +}
 +
 +/**
 + * gvc_mixer_control_get_sinks:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerSink):
 + */
 +GSList *
 +gvc_mixer_control_get_sinks (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->sinks,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 +}
 +
 +/**
 + * gvc_mixer_control_get_sources:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerSource):
 + */
 +GSList *
 +gvc_mixer_control_get_sources (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->sources,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 +}
 +
 +/**
 + * gvc_mixer_control_get_sink_inputs:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerSinkInput):
 + */
 +GSList *
 +gvc_mixer_control_get_sink_inputs (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->sink_inputs,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 +}
 +
 +/**
 + * gvc_mixer_control_get_source_outputs:
 + * @control:
 + *
 + * Returns: (transfer container) (element-type Gvc.MixerSourceOutput):
 + */
 +GSList *
 +gvc_mixer_control_get_source_outputs (GvcMixerControl *control)
 +{
 +        GSList *retval;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), NULL);
 +
 +        retval = NULL;
 +        g_hash_table_foreach (control->priv->source_outputs,
 +                              listify_hash_values_hfunc,
 +                              &retval);
 +        return g_slist_sort (retval, (GCompareFunc) gvc_stream_collate);
 +}
 +
 +static void
 +dec_outstanding (GvcMixerControl *control)
 +{
 +        if (control->priv->n_outstanding <= 0) {
 +                return;
 +        }
 +
 +        if (--control->priv->n_outstanding <= 0) {
 +                control->priv->state = GVC_STATE_READY;
 +                g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_READY);
 +        }
 +}
 +
 +GvcMixerControlState
 +gvc_mixer_control_get_state (GvcMixerControl *control)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), GVC_STATE_CLOSED);
 +
 +        return control->priv->state;
 +}
 +
 +static void
 +on_default_source_port_notify (GObject        *object,
 +                               GParamSpec     *pspec,
 +                               GvcMixerControl *control)
 +{
 +        char             *port;
 +        GvcMixerUIDevice *input;
 +
 +        g_object_get (object, "port", &port, NULL);
 +        input = gvc_mixer_control_lookup_device_from_stream (control,
 +                                                             GVC_MIXER_STREAM (object));
 +
 +        g_debug ("on_default_source_port_notify - moved to port '%s' which SHOULD ?? correspond to output '%s'",
 +                 port,
 +                 gvc_mixer_ui_device_get_description (input));
 +
 +        g_signal_emit (G_OBJECT (control),
 +                       signals[ACTIVE_INPUT_UPDATE],
 +                       0,
 +                       gvc_mixer_ui_device_get_id (input));
 +
 +        g_free (port);
 +}
 +
 +
 +static void
 +_set_default_source (GvcMixerControl *control,
 +                     GvcMixerStream  *stream)
 +{
 +        guint new_id;
 +
 +        if (stream == NULL) {
 +                if (!control->priv->default_source_is_set)
 +                        return;
 +
 +                g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
 +                                                      on_default_source_port_notify,
 +                                                      control);
 +
 +                control->priv->default_source_id = 0;
 +                control->priv->default_source_is_set = FALSE;
 +                g_signal_emit (control,
 +                               signals[DEFAULT_SOURCE_CHANGED],
 +                               0,
 +                               PA_INVALID_INDEX);
 +                return;
 +        }
 +
 +        new_id = gvc_mixer_stream_get_id (stream);
 +
 +        if (control->priv->default_source_id != new_id) {
 +                GvcMixerUIDevice *input;
 +
 +                if (control->priv->default_source_is_set) {
 +                        g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_source (control),
 +                                                              on_default_source_port_notify,
 +                                                              control);
 +                }
 +
 +                g_signal_connect (stream,
 +                                  "notify::port",
 +                                  G_CALLBACK (on_default_source_port_notify),
 +                                  control);
 +
 +                control->priv->default_source_id = new_id;
 +                control->priv->default_source_is_set = TRUE;
 +                g_signal_emit (control,
 +                               signals[DEFAULT_SOURCE_CHANGED],
 +                               0,
 +                               new_id);
 +
 +                input = gvc_mixer_control_lookup_device_from_stream (control, stream);
 +                if (input) {
 +                        g_signal_emit (G_OBJECT (control),
 +                                       signals[ACTIVE_INPUT_UPDATE],
 +                                       0,
 +                                       gvc_mixer_ui_device_get_id (input));
 +                } else {
 +                        g_warning ("Can't find input for stream-id %d",new_id);
 +                }
 +        }
 +}
 +
 +static void
 +on_default_sink_port_notify (GObject        *object,
 +                             GParamSpec     *pspec,
 +                             GvcMixerControl *control)
 +{
 +        char             *port;
 +        GvcMixerUIDevice *output;
 +
 +        g_object_get (object, "port", &port, NULL);
 +
 +        output = gvc_mixer_control_lookup_device_from_stream (control,
 +                                                              GVC_MIXER_STREAM (object));
 +        if (output != NULL) {
 +                g_debug ("on_default_sink_port_notify - moved to port %s - which SHOULD correspond to output %s",
 +                         port,
 +                         gvc_mixer_ui_device_get_description (output));
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[ACTIVE_OUTPUT_UPDATE],
 +                               0,
 +                               gvc_mixer_ui_device_get_id (output));
 +        }
 +        g_free (port);
 +}
 +
 +static void
 +_set_default_sink (GvcMixerControl *control,
 +                   GvcMixerStream  *stream)
 +{
 +        guint new_id;
 +
 +        if (stream == NULL) {
 +                /* Don't tell front-ends about an unset default
 +                 * sink if it's already unset */
 +                if (control->priv->default_sink_is_set == FALSE)
 +                        return;
 +
 +                g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
 +                                                      on_default_sink_port_notify,
 +                                                      control);
 +
 +                control->priv->default_sink_id = 0;
 +                control->priv->default_sink_is_set = FALSE;
 +                g_signal_emit (control,
 +                               signals[DEFAULT_SINK_CHANGED],
 +                               0,
 +                               PA_INVALID_INDEX);
 +                return;
 +        }
 +
 +        new_id = gvc_mixer_stream_get_id (stream);
 +
 +        if (control->priv->default_sink_id != new_id) {
 +                GvcMixerUIDevice *output;
 +                if (control->priv->default_sink_is_set) {
 +                        g_signal_handlers_disconnect_by_func (gvc_mixer_control_get_default_sink (control),
 +                                                              on_default_sink_port_notify,
 +                                                              control);
 +                }
 +
 +                control->priv->default_sink_id = new_id;
 +
 +                control->priv->default_sink_is_set = TRUE;
 +                g_signal_emit (control,
 +                               signals[DEFAULT_SINK_CHANGED],
 +                               0,
 +                               new_id);
 +
 +                g_signal_connect (stream,
 +                                  "notify::port",
 +                                  G_CALLBACK (on_default_sink_port_notify),
 +                                  control);
 +
 +                output = gvc_mixer_control_lookup_device_from_stream (control, stream);
 +
 +                g_debug ("active_sink change");
 +
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[ACTIVE_OUTPUT_UPDATE],
 +                               0,
 +                               gvc_mixer_ui_device_get_id (output));
 +        }
 +}
 +
 +static gboolean
 +_stream_has_name (gpointer        key,
 +                  GvcMixerStream *stream,
 +                  const char     *name)
 +{
 +        const char *t_name;
 +
 +        t_name = gvc_mixer_stream_get_name (stream);
 +
 +        if (t_name != NULL
 +            && name != NULL
 +            && strcmp (t_name, name) == 0) {
 +                return TRUE;
 +        }
 +
 +        return FALSE;
 +}
 +
 +static GvcMixerStream *
 +find_stream_for_name (GvcMixerControl *control,
 +                      const char      *name)
 +{
 +        GvcMixerStream *stream;
 +
 +        stream = g_hash_table_find (control->priv->all_streams,
 +                                    (GHRFunc)_stream_has_name,
 +                                    (char *)name);
 +        return stream;
 +}
 +
 +static void
 +update_default_source_from_name (GvcMixerControl *control,
 +                                 const char      *name)
 +{
 +        gboolean changed = FALSE;
 +
 +        if ((control->priv->default_source_name == NULL
 +             && name != NULL)
 +            || (control->priv->default_source_name != NULL
 +                && name == NULL)
 +            || (name != NULL && strcmp (control->priv->default_source_name, name) != 0)) {
 +                changed = TRUE;
 +        }
 +
 +        if (changed) {
 +                GvcMixerStream *stream;
 +
 +                g_free (control->priv->default_source_name);
 +                control->priv->default_source_name = g_strdup (name);
 +
 +                stream = find_stream_for_name (control, name);
 +                _set_default_source (control, stream);
 +        }
 +}
 +
 +static void
 +update_default_sink_from_name (GvcMixerControl *control,
 +                               const char      *name)
 +{
 +        gboolean changed = FALSE;
 +
 +        if ((control->priv->default_sink_name == NULL
 +             && name != NULL)
 +            || (control->priv->default_sink_name != NULL
 +                && name == NULL)
 +            || (name != NULL && strcmp (control->priv->default_sink_name, name) != 0)) {
 +                changed = TRUE;
 +        }
 +
 +        if (changed) {
 +                GvcMixerStream *stream;
 +                g_free (control->priv->default_sink_name);
 +                control->priv->default_sink_name = g_strdup (name);
 +
 +                stream = find_stream_for_name (control, name);
 +                _set_default_sink (control, stream);
 +        }
 +}
 +
 +static void
 +update_server (GvcMixerControl      *control,
 +               const pa_server_info *info)
 +{
 +        if (info->default_source_name != NULL) {
 +                update_default_source_from_name (control, info->default_source_name);
 +        }
 +        if (info->default_sink_name != NULL) {
 +                g_debug ("update server");
 +                update_default_sink_from_name (control, info->default_sink_name);
 +        }
 +}
 +
 +static void
 +remove_stream (GvcMixerControl *control,
 +               GvcMixerStream  *stream)
 +{
 +        guint id;
 +
 +        g_object_ref (stream);
 +
 +        id = gvc_mixer_stream_get_id (stream);
 +
 +        if (id == control->priv->default_sink_id) {
 +                _set_default_sink (control, NULL);
 +        } else if (id == control->priv->default_source_id) {
 +                _set_default_source (control, NULL);
 +        }
 +
 +        g_hash_table_remove (control->priv->all_streams,
 +                             GUINT_TO_POINTER (id));
 +        g_signal_emit (G_OBJECT (control),
 +                       signals[STREAM_REMOVED],
 +                       0,
 +                       gvc_mixer_stream_get_id (stream));
 +        g_object_unref (stream);
 +}
 +
 +/**
 + * add_stream: (skip)
 + * @control: The control to add the stream to
 + * @stream:(transfer full): The stream to add
 + *
 + * Add a stream to the hash table of all known streams
 + */
 +static void
 +add_stream (GvcMixerControl *control,
 +            GvcMixerStream  *stream)
 +{
 +        g_hash_table_insert (control->priv->all_streams,
 +                             GUINT_TO_POINTER (gvc_mixer_stream_get_id (stream)),
 +                             stream);
 +        g_signal_emit (G_OBJECT (control),
 +                       signals[STREAM_ADDED],
 +                       0,
 +                       gvc_mixer_stream_get_id (stream));
 +}
 +
 +/* This method will match individual stream ports against its corresponding device
 + * It does this by:
 + * - iterates through our devices and finds the one where the card-id on the device is the same as the card-id on the stream
 + *   and the port-name on the device is the same as the streamport-name.
 + * This should always find a match and is used exclusively by sync_devices().
 + */
 +static gboolean
 +match_stream_with_devices (GvcMixerControl    *control,
 +                           GvcMixerStreamPort *stream_port,
-                                         if (device_stream_id == stream_id) {
++                           GvcMixerStream     *stream,
++                           gboolean            is_bluetooth)
 +{
 +        g_autoptr (GList)        devices = NULL;
 +        guint                    stream_card_id;
 +        guint                    stream_id;
 +        gboolean                 in_possession = FALSE;
 +
 +        stream_id      =  gvc_mixer_stream_get_id (stream);
 +        stream_card_id =  gvc_mixer_stream_get_card_index (stream);
 +
 +        devices  = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
 +
 +        for (GList *d = devices; d != NULL; d = d->next) {
 +                GvcMixerUIDevice *device;
 +                guint             device_stream_id;
 +                g_autofree gchar *device_port_name = NULL;
 +                g_autofree gchar *origin = NULL;
 +                g_autofree gchar *description = NULL;
 +                GvcMixerCard     *card;
 +                guint             card_id;
 +
 +                device = d->data;
 +                g_object_get (G_OBJECT (device),
 +                             "stream-id", &device_stream_id,
 +                             "card", &card,
 +                             "origin", &origin,
 +                             "description", &description,
 +                             "port-name", &device_port_name,
 +                              NULL);
 +
 +                if (card == NULL) {
 +                        if (device_stream_id == stream_id) {
 +                                g_debug ("Matched stream %u with card-less device '%s', with stream already setup",
 +                                         stream_id, description);
 +                                in_possession = TRUE;
 +                        }
 +                } else {
 +                        card_id = gvc_mixer_card_get_index (card);
 +
 +                        g_debug ("Attempt to match_stream update_with_existing_outputs - Try description : '%s', origin : '%s', device port name : '%s', card : %p, AGAINST stream port: '%s', sink card id %i",
 +                                 description,
 +                                 origin,
 +                                 device_port_name,
 +                                 card,
 +                                 stream_port->port,
 +                                 stream_card_id);
 +
 +                        if (stream_card_id == card_id) {
 +                                if (g_strcmp0 (device_port_name, stream_port->port) == 0) {
 +                                        g_debug ("Match device with stream: We have a match with description: '%s', origin: '%s', cached already with device id %u, so set stream id to %i",
 +                                                 description,
 +                                                 origin,
 +                                                 gvc_mixer_ui_device_get_id (device),
 +                                                 stream_id);
 +
 +                                        g_object_set (G_OBJECT (device),
 +                                                      "stream-id", stream_id,
 +                                                      NULL);
 +                                        in_possession = TRUE;
 +                                } else {
 +                                        /* Other UI devices on that card that match the stream can't be valid */
-                 if (match_stream_with_devices (control, stream_port, stream))
++                                        if (device_stream_id == stream_id && is_bluetooth) {
 +                                                g_object_set (G_OBJECT (device),
 +                                                              "stream-id", GVC_MIXER_UI_DEVICE_INVALID,
 +                                                              NULL);
 +                                                g_debug ("Found another UI device for this stream: %d, resetting", stream_id);
 +                                        }
 +                                }
 +                        }
 +                }
 +        }
 +
 +        return in_possession;
 +}
 +
 +
 +/* Clear stream-id from all UI devices that reference the stream that lost all ports */
 +static void
 +clear_stream_from_devices (GvcMixerControl    *control,
 +                           GvcMixerStream     *stream)
 +{
 +        g_autoptr (GList)        devices = NULL;
 +        guint                    stream_id;
 +
 +        stream_id = gvc_mixer_stream_get_id (stream);
 +        devices = g_hash_table_get_values (GVC_IS_MIXER_SOURCE (stream) ? control->priv->ui_inputs : control->priv->ui_outputs);
 +
 +        for (GList *d = devices; d != NULL; d = d->next) {
 +                guint             device_stream_id;
 +                GvcMixerUIDevice *device = d->data;
 +
 +                g_object_get (G_OBJECT (device),
 +                              "stream-id", &device_stream_id,
 +                              NULL);
 +
 +                if (device_stream_id == stream_id) {
 +                        g_object_set (G_OBJECT (device),
 +                                      "stream-id", GVC_MIXER_UI_DEVICE_INVALID,
 +                                      NULL);
 +                        g_debug ("Clearing stream-id: %d", stream_id);
 +                }
 +        }
 +}
 +
 +/*
 + * This method attempts to match a sink or source with its relevant UI device.
 + * GvcMixerStream can represent both a sink or source.
 + * Using static card port introspection implies that we know beforehand what
 + * outputs and inputs are available to the user.
 + * But that does not mean that all of these inputs and outputs are available to be used.
 + * For instance we might be able to see that there is a HDMI port available but if
 + * we are on the default analog stereo output profile there is no valid sink for
 + * that HDMI device. We first need to change profile and when update_sink() is called
 + * only then can we match the new hdmi sink with its corresponding device.
 + *
 + * Firstly it checks to see if the incoming stream has no ports.
 + * - If a stream has no ports and no valid card id, it goes ahead and makes a new
 + *   device (software/network devices are only detectable at the sink/source level)
 + * If the stream has ports it will match each port against the stream using match_stream_with_devices().
 + *
 + * This method should always find a match.
 + */
 +static void
 +sync_devices (GvcMixerControl *control,
 +              GvcMixerStream*  stream,
 +              gboolean         is_new,
 +              gboolean         is_bluetooth)
 +{
 +        /* Go through ports to see what outputs can be created. */
 +        const GList *stream_ports;
 +        const GList *n = NULL;
 +        gboolean     is_output = !GVC_IS_MIXER_SOURCE (stream);
 +
 +        stream_ports = gvc_mixer_stream_get_ports (stream);
 +
 +        if (stream_ports == NULL && is_bluetooth) {
 +                clear_stream_from_devices (control, stream);
 +                return;
 +        }
 +
 +        if (stream_ports == NULL && is_new) {
 +                GvcMixerUIDevice *device;
 +                GObject *object;
 +
 +                object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 +                                       "stream-id", gvc_mixer_stream_get_id (stream),
 +                                       "description", gvc_mixer_stream_get_description (stream),
 +                                       "origin", "", /* Leave it empty for these special cases */
 +                                       "port-name", NULL,
 +                                       "port-available", TRUE,
 +                                       "icon-name", gvc_mixer_stream_get_icon_name (stream),
 +                                        NULL);
 +                device = GVC_MIXER_UI_DEVICE (object);
 +
 +                g_hash_table_insert (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
 +                                     GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)),
 +                                     device);
 +
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[is_output ? OUTPUT_ADDED : INPUT_ADDED],
 +                               0,
 +                               gvc_mixer_ui_device_get_id (device));
 +
 +                return;
 +        }
 +
 +        /* Go ahead and make sure to match each port against a previously created device */
 +        for (n = stream_ports; n != NULL; n = n->next) {
 +
 +                GvcMixerStreamPort *stream_port;
 +                stream_port = n->data;
 +
++                if (match_stream_with_devices (control, stream_port, stream, is_bluetooth))
 +                        continue;
 +
 +                g_warning ("Sync_devices: Failed to match stream id: %u, description: '%s', origin: '%s'",
 +                           gvc_mixer_stream_get_id (stream),
 +                           stream_port->human_port,
 +                           gvc_mixer_stream_get_description (stream));
 +        }
 +}
 +
 +static void
 +set_icon_name_from_proplist (GvcMixerStream *stream,
 +                             pa_proplist    *l,
 +                             const char     *default_icon_name)
 +{
 +        const char *t;
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_DEVICE_ICON_NAME))) {
 +                goto finish;
 +        }
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ICON_NAME))) {
 +                goto finish;
 +        }
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_WINDOW_ICON_NAME))) {
 +                goto finish;
 +        }
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ICON_NAME))) {
 +                goto finish;
 +        }
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
 +
 +                if (strcmp (t, "video") == 0 ||
 +                    strcmp (t, "phone") == 0) {
 +                        goto finish;
 +                }
 +
 +                if (strcmp (t, "music") == 0) {
 +                        t = "audio";
 +                        goto finish;
 +                }
 +
 +                if (strcmp (t, "game") == 0) {
 +                        t = "applications-games";
 +                        goto finish;
 +                }
 +
 +                if (strcmp (t, "event") == 0) {
 +                        t = "dialog-information";
 +                        goto finish;
 +                }
 +        }
 +
 +        t = default_icon_name;
 +
 + finish:
 +        gvc_mixer_stream_set_icon_name (stream, t);
 +}
 +
 +static GvcMixerStreamState
 +translate_pa_state (pa_sink_state_t state) {
 +        switch (state) {
 +        case PA_SINK_RUNNING:
 +                return GVC_STREAM_STATE_RUNNING;
 +        case PA_SINK_IDLE:
 +                return GVC_STREAM_STATE_IDLE;
 +        case PA_SINK_SUSPENDED:
 +                return GVC_STREAM_STATE_SUSPENDED;
 +        case PA_SINK_INIT:
 +        case PA_SINK_INVALID_STATE:
 +        case PA_SINK_UNLINKED:
 +        default:
 +                return GVC_STREAM_STATE_INVALID;
 +        }
 +}
 +
 +static gboolean
 +is_bluetooth (const pa_proplist *proplist)
 +{
 +        const char *bus = pa_proplist_gets (proplist, "device.bus");
 +
 +        if (bus == NULL)
 +                return FALSE;
 +
 +        if (g_str_equal ("bluetooth", bus))
 +                return TRUE;
 +
 +        return FALSE;
 +}
 +
 +/*
 + * Called when anything changes with a sink.
 + */
 +static void
 +update_sink (GvcMixerControl    *control,
 +             const pa_sink_info *info)
 +{
 +        GvcMixerStream  *stream;
 +        gboolean        is_new;
 +        pa_volume_t     max_volume;
 +        GvcChannelMap   *map;
 +        char            map_buff[PA_CHANNEL_MAP_SNPRINT_MAX];
 +        GList          *list = NULL;
 +        gboolean        is_bt;
 +
 +        pa_channel_map_snprint (map_buff, PA_CHANNEL_MAP_SNPRINT_MAX, &info->channel_map);
 +#if 1
 +        g_debug ("Updating sink: index=%u name='%s' description='%s' map='%s'",
 +                 info->index,
 +                 info->name,
 +                 info->description,
 +                 map_buff);
 +#endif
 +
 +        map = NULL;
 +        is_new = FALSE;
 +        stream = g_hash_table_lookup (control->priv->sinks,
 +                                      GUINT_TO_POINTER (info->index));
 +
 +        if (stream == NULL) {
 +                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 +                stream = gvc_mixer_sink_new (control->priv->pa_context,
 +                                             info->index,
 +                                             map);
 +                g_object_unref (map);
 +                is_new = TRUE;
 +
 +        } else if (gvc_mixer_stream_is_running (stream)) {
 +                /* Ignore events if volume changes are outstanding */
 +                g_debug ("Ignoring event, volume changes are outstanding");
 +                return;
 +        }
 +
 +        for (guint i = 0; i < info->n_ports; i++) {
 +                GvcMixerStreamPort *port;
 +
 +                port = g_slice_new0 (GvcMixerStreamPort);
 +                port->port = g_strdup (info->ports[i]->name);
 +                port->human_port = g_strdup (info->ports[i]->description);
 +                port->priority = info->ports[i]->priority;
 +                port->available = info->ports[i]->available != PA_PORT_AVAILABLE_NO;
 +
 +                list = g_list_prepend (list, port);
 +        }
 +        gvc_mixer_stream_set_ports (stream, g_steal_pointer (&list));
 +
 +        max_volume = pa_cvolume_max (&info->volume);
 +        gvc_mixer_stream_set_name (stream, info->name);
 +        gvc_mixer_stream_set_card_index (stream, info->card);
 +        gvc_mixer_stream_set_description (stream, info->description);
 +        set_icon_name_from_proplist (stream, info->proplist, "audio-card");
 +        gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
 +        gvc_mixer_stream_set_sysfs_path (stream, pa_proplist_gets (info->proplist, "sysfs.path"));
 +        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 +        gvc_mixer_stream_set_is_muted (stream, info->mute);
 +        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SINK_DECIBEL_VOLUME));
 +        gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
 +        gvc_mixer_stream_set_state (stream, translate_pa_state (info->state));
 +
 +        is_bt = is_bluetooth (info->proplist);
 +        /* Sync devices as the port on the stream might have changed */
 +        if (!is_new)
 +                sync_devices (control, stream, is_new, is_bt);
 +
 +        /* Messy I know but to set the port everytime regardless of whether it has changed will cost us a
 +         * port change notify signal which causes the frontend to resync.
 +         * Only update the UI when something has changed. */
 +        if (info->active_port != NULL) {
 +                if (is_new)
 +                        gvc_mixer_stream_set_port (stream, info->active_port->name);
 +                else {
 +                        const GvcMixerStreamPort *active_port;
 +                        active_port = gvc_mixer_stream_get_port (stream);
 +                        if (active_port == NULL ||
 +                            g_strcmp0 (active_port->port, info->active_port->name) != 0) {
 +                                g_debug ("update sink - apparently a port update");
 +                                if (!gvc_mixer_stream_set_port (stream, info->active_port->name))
 +                                        g_warning ("Port update to of %d '%s' failed", info->index, info->active_port->name);
 +                        }
 +                }
 +        }
 +
 +        if (is_new) {
 +                g_debug ("update sink - is new");
 +
 +                g_hash_table_insert (control->priv->sinks,
 +                                     GUINT_TO_POINTER (info->index),
 +                                     g_object_ref (stream));
 +                add_stream (control, stream);
 +                /* Always sync on a new stream to able to assign the right stream id
 +                 * to the appropriate outputs (multiple potential outputs per stream). */
 +                sync_devices (control, stream, is_new, is_bt);
 +        } else {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[STREAM_CHANGED],
 +                               0,
 +                               gvc_mixer_stream_get_id (stream));
 +        }
 +
 +        /*
 +         * When we change profile on a device that is not the server default sink,
 +         * it will jump back to the default sink set by the server to prevent the audio setup from being 'outputless'.
 +         * All well and good but then when we get the new stream created for the new profile how do we know
 +         * that this is the intended default or selected device the user wishes to use.
 +         * This is messy but it's the only reliable way that it can be done without ripping the whole thing apart.
 +         */
 +        if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
 +                GvcMixerUIDevice *dev = NULL;
 +                dev = gvc_mixer_control_lookup_output_id (control, control->priv->profile_swapping_device_id);
 +                if (dev != NULL) {
 +                        /* now check to make sure this new stream is the same stream just matched and set on the device object */
 +                        if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
 +                                g_debug ("Looks like we profile swapped on a non server default sink");
 +                                gvc_mixer_control_set_default_sink (control, stream);
 +                                control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 +                        }
 +                }
 +        }
 +
 +        if (control->priv->default_sink_name != NULL
 +            && info->name != NULL
 +            && strcmp (control->priv->default_sink_name, info->name) == 0) {
 +                _set_default_sink (control, stream);
 +        }
 +
 +        if (map == NULL)
 +                map = (GvcChannelMap *) gvc_mixer_stream_get_channel_map (stream);
 +
 +        gvc_channel_map_volume_changed (map, &info->volume, FALSE);
 +}
 +
 +
 +static void
 +update_source (GvcMixerControl      *control,
 +               const pa_source_info *info)
 +{
 +        GvcMixerStream *stream;
 +        gboolean        is_new;
 +        pa_volume_t     max_volume;
 +        GList          *list = NULL;
 +        gboolean        is_bt;
 +
 +#if 1
 +        g_debug ("Updating source: index=%u name='%s' description='%s'",
 +                 info->index,
 +                 info->name,
 +                 info->description);
 +#endif
 +
 +        /* completely ignore monitors, they're not real sources */
 +        if (info->monitor_of_sink != PA_INVALID_INDEX) {
 +                return;
 +        }
 +
 +        is_new = FALSE;
 +
 +        stream = g_hash_table_lookup (control->priv->sources,
 +                                      GUINT_TO_POINTER (info->index));
 +        if (stream == NULL) {
 +                GvcChannelMap *map;
 +
 +                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 +                stream = gvc_mixer_source_new (control->priv->pa_context,
 +                                               info->index,
 +                                               map);
 +                g_object_unref (map);
 +                is_new = TRUE;
 +        } else if (gvc_mixer_stream_is_running (stream)) {
 +                /* Ignore events if volume changes are outstanding */
 +                g_debug ("Ignoring event, volume changes are outstanding");
 +                return;
 +        }
 +
 +        for (guint i = 0; i < info->n_ports; i++) {
 +                GvcMixerStreamPort *port;
 +
 +                port = g_slice_new0 (GvcMixerStreamPort);
 +                port->port = g_strdup (info->ports[i]->name);
 +                port->human_port = g_strdup (info->ports[i]->description);
 +                port->priority = info->ports[i]->priority;
 +                list = g_list_prepend (list, port);
 +        }
 +        gvc_mixer_stream_set_ports (stream, list);
 +
 +        max_volume = pa_cvolume_max (&info->volume);
 +
 +        gvc_mixer_stream_set_name (stream, info->name);
 +        gvc_mixer_stream_set_card_index (stream, info->card);
 +        gvc_mixer_stream_set_description (stream, info->description);
 +        set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
 +        gvc_mixer_stream_set_form_factor (stream, pa_proplist_gets (info->proplist, PA_PROP_DEVICE_FORM_FACTOR));
 +        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 +        gvc_mixer_stream_set_is_muted (stream, info->mute);
 +        gvc_mixer_stream_set_can_decibel (stream, !!(info->flags & PA_SOURCE_DECIBEL_VOLUME));
 +        gvc_mixer_stream_set_base_volume (stream, (guint32) info->base_volume);
 +        g_debug ("update source");
 +
 +        is_bt = is_bluetooth (info->proplist);
 +        /* Sync devices as the port on the stream might have changed */
 +        if (!is_new)
 +                sync_devices (control, stream, is_new, is_bt);
 +
 +        if (info->active_port != NULL) {
 +                if (is_new)
 +                        gvc_mixer_stream_set_port (stream, info->active_port->name);
 +                else {
 +                        const GvcMixerStreamPort *active_port;
 +                        active_port = gvc_mixer_stream_get_port (stream);
 +                        if (active_port == NULL ||
 +                            g_strcmp0 (active_port->port, info->active_port->name) != 0) {
 +                                g_debug ("update source - apparently a port update");
 +                                if (!gvc_mixer_stream_set_port (stream, info->active_port->name))
 +                                        g_warning ("Port update to of %d '%s' failed", info->index, info->active_port->name);
 +                        }
 +                }
 +        }
 +
 +        if (is_new) {
 +                g_hash_table_insert (control->priv->sources,
 +                                     GUINT_TO_POINTER (info->index),
 +                                     g_object_ref (stream));
 +                add_stream (control, stream);
 +                sync_devices (control, stream, is_new, is_bt);
 +        } else {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[STREAM_CHANGED],
 +                               0,
 +                               gvc_mixer_stream_get_id (stream));
 +        }
 +
 +        if (control->priv->profile_swapping_device_id != GVC_MIXER_UI_DEVICE_INVALID) {
 +                GvcMixerUIDevice *dev = NULL;
 +
 +                dev = gvc_mixer_control_lookup_input_id (control, control->priv->profile_swapping_device_id);
 +
 +                if (dev != NULL) {
 +                        /* now check to make sure this new stream is the same stream just matched and set on the device object */
 +                        if (gvc_mixer_ui_device_get_stream_id (dev) == gvc_mixer_stream_get_id (stream)) {
 +                                g_debug ("Looks like we profile swapped on a non server default source");
 +                                gvc_mixer_control_set_default_source (control, stream);
 +                                control->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 +                        }
 +                }
 +        }
 +        if (control->priv->default_source_name != NULL
 +            && info->name != NULL
 +            && strcmp (control->priv->default_source_name, info->name) == 0) {
 +                _set_default_source (control, stream);
 +        }
 +}
 +
 +static void
 +set_is_event_stream_from_proplist (GvcMixerStream *stream,
 +                                   pa_proplist    *l)
 +{
 +        const char *t;
 +        gboolean is_event_stream;
 +
 +        is_event_stream = FALSE;
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_MEDIA_ROLE))) {
 +                if (g_str_equal (t, "event"))
 +                        is_event_stream = TRUE;
 +        }
 +
 +        gvc_mixer_stream_set_is_event_stream (stream, is_event_stream);
 +}
 +
 +static void
 +set_application_id_from_proplist (GvcMixerStream *stream,
 +                                  pa_proplist    *l)
 +{
 +        const char *t;
 +
 +        if ((t = pa_proplist_gets (l, PA_PROP_APPLICATION_ID))) {
 +                gvc_mixer_stream_set_application_id (stream, t);
 +        }
 +}
 +
 +static void
 +update_sink_input (GvcMixerControl          *control,
 +                   const pa_sink_input_info *info)
 +{
 +        GvcMixerStream *stream;
 +        gboolean        is_new;
 +        pa_volume_t     max_volume;
 +        const char     *name;
 +
 +#if 0
 +        g_debug ("Updating sink input: index=%u name='%s' client=%u sink=%u",
 +                 info->index,
 +                 info->name,
 +                 info->client,
 +                 info->sink);
 +#endif
 +
 +        is_new = FALSE;
 +
 +        stream = g_hash_table_lookup (control->priv->sink_inputs,
 +                                      GUINT_TO_POINTER (info->index));
 +        if (stream == NULL) {
 +                GvcChannelMap *map;
 +                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 +                stream = gvc_mixer_sink_input_new (control->priv->pa_context,
 +                                                   info->index,
 +                                                   map);
 +                g_object_unref (map);
 +                is_new = TRUE;
 +        } else if (gvc_mixer_stream_is_running (stream)) {
 +                /* Ignore events if volume changes are outstanding */
 +                g_debug ("Ignoring event, volume changes are outstanding");
 +                return;
 +        }
 +
 +        max_volume = pa_cvolume_max (&info->volume);
 +
 +        name = (const char *)g_hash_table_lookup (control->priv->clients,
 +                                                  GUINT_TO_POINTER (info->client));
 +        gvc_mixer_stream_set_name (stream, name);
 +        gvc_mixer_stream_set_description (stream, info->name);
 +
 +        set_application_id_from_proplist (stream, info->proplist);
 +        set_is_event_stream_from_proplist (stream, info->proplist);
 +        set_icon_name_from_proplist (stream, info->proplist, "application-x-executable");
 +        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 +        gvc_mixer_stream_set_is_muted (stream, info->mute);
 +        gvc_mixer_stream_set_is_virtual (stream, info->client == PA_INVALID_INDEX);
 +
 +        if (is_new) {
 +                g_hash_table_insert (control->priv->sink_inputs,
 +                                     GUINT_TO_POINTER (info->index),
 +                                     g_object_ref (stream));
 +                add_stream (control, stream);
 +        } else {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[STREAM_CHANGED],
 +                               0,
 +                               gvc_mixer_stream_get_id (stream));
 +        }
 +}
 +
 +static void
 +update_source_output (GvcMixerControl             *control,
 +                      const pa_source_output_info *info)
 +{
 +        GvcMixerStream *stream;
 +        gboolean        is_new;
 +        pa_volume_t     max_volume;
 +        const char     *name;
 +
 +#if 1
 +        g_debug ("Updating source output: index=%u name='%s' client=%u source=%u",
 +                 info->index,
 +                 info->name,
 +                 info->client,
 +                 info->source);
 +#endif
 +
 +        is_new = FALSE;
 +        stream = g_hash_table_lookup (control->priv->source_outputs,
 +                                      GUINT_TO_POINTER (info->index));
 +        if (stream == NULL) {
 +                GvcChannelMap *map;
 +                map = gvc_channel_map_new_from_pa_channel_map (&info->channel_map);
 +                stream = gvc_mixer_source_output_new (control->priv->pa_context,
 +                                                      info->index,
 +                                                      map);
 +                g_object_unref (map);
 +                is_new = TRUE;
 +        }
 +
 +        name = (const char *)g_hash_table_lookup (control->priv->clients,
 +                                                  GUINT_TO_POINTER (info->client));
 +
 +        max_volume = pa_cvolume_max (&info->volume);
 +
 +        gvc_mixer_stream_set_name (stream, name);
 +        gvc_mixer_stream_set_description (stream, info->name);
 +        set_application_id_from_proplist (stream, info->proplist);
 +        set_is_event_stream_from_proplist (stream, info->proplist);
 +        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 +        gvc_mixer_stream_set_is_muted (stream, info->mute);
 +        set_icon_name_from_proplist (stream, info->proplist, "audio-input-microphone");
 +
 +        if (is_new) {
 +                g_hash_table_insert (control->priv->source_outputs,
 +                                     GUINT_TO_POINTER (info->index),
 +                                     g_object_ref (stream));
 +                add_stream (control, stream);
 +        } else {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[STREAM_CHANGED],
 +                               0,
 +                               gvc_mixer_stream_get_id (stream));
 +        }
 +}
 +
 +static void
 +update_client (GvcMixerControl      *control,
 +               const pa_client_info *info)
 +{
 +#if 1
 +        g_debug ("Updating client: index=%u name='%s'",
 +                 info->index,
 +                 info->name);
 +#endif
 +        g_hash_table_insert (control->priv->clients,
 +                             GUINT_TO_POINTER (info->index),
 +                             g_strdup (info->name));
 +}
 +
 +static char *
 +card_num_streams_to_status (guint sinks,
 +                            guint sources)
 +{
 +        char *sinks_str;
 +        char *sources_str;
 +        char *ret;
 +
 +        if (sinks == 0 && sources == 0) {
 +                /* translators:
 +                 * The device has been disabled */
 +                return g_strdup (_("Disabled"));
 +        }
 +        if (sinks == 0) {
 +                sinks_str = NULL;
 +        } else {
 +                /* translators:
 +                 * The number of sound outputs on a particular device */
 +                sinks_str = g_strdup_printf (ngettext ("%u Output",
 +                                                       "%u Outputs",
 +                                                       sinks),
 +                                             sinks);
 +        }
 +        if (sources == 0) {
 +                sources_str = NULL;
 +        } else {
 +                /* translators:
 +                 * The number of sound inputs on a particular device */
 +                sources_str = g_strdup_printf (ngettext ("%u Input",
 +                                                         "%u Inputs",
 +                                                         sources),
 +                                               sources);
 +        }
 +        if (sources_str == NULL)
 +                return sinks_str;
 +        if (sinks_str == NULL)
 +                return sources_str;
 +        ret = g_strdup_printf ("%s / %s", sinks_str, sources_str);
 +        g_free (sinks_str);
 +        g_free (sources_str);
 +        return ret;
 +}
 +
 +/*
 + * A utility method to gather which card profiles are relevant to the port .
 + */
 +static GList *
 +determine_profiles_for_port (pa_card_port_info *port,
 +                             const GList       *card_profiles)
 +{
 +        guint i;
 +        GList *supported_profiles = NULL;
 +        const GList *p;
 +        for (i = 0; i < port->n_profiles; i++) {
 +                for (p = card_profiles; p != NULL; p = p->next) {
 +                        GvcMixerCardProfile *prof;
 +                        prof = p->data;
 +                        if (g_strcmp0 (port->profiles[i]->name, prof->profile) == 0)
 +                                supported_profiles = g_list_append (supported_profiles, prof);
 +                }
 +        }
 +        g_debug ("%i profiles supported on port %s / %s",
 +                 g_list_length (supported_profiles),
 +                 port->description, port->name);
 +        return g_list_sort (supported_profiles, (GCompareFunc) gvc_mixer_card_profile_compare);
 +}
 +
 +static gboolean
 +is_card_port_an_output (GvcMixerCardPort* port)
 +{
 +        return port->direction == PA_DIRECTION_OUTPUT ? TRUE : FALSE;
 +}
 +
 +/*
 + * This method will create a ui device for the given port.
 + */
 +static void
 +update_ui_device_on_port_added (GvcMixerControl  *control,
 +                                GvcMixerCardPort *port,
 +                                GvcMixerCard     *card)
 +{
 +        GvcMixerUIDeviceDirection  direction;
 +        GObject                   *object;
 +        GvcMixerUIDevice          *uidevice;
 +        gboolean                   available = port->available != PA_PORT_AVAILABLE_NO;
 +
 +        direction = (is_card_port_an_output (port) == TRUE) ? UIDeviceOutput : UIDeviceInput;
 +
 +        object = g_object_new (GVC_TYPE_MIXER_UI_DEVICE,
 +                               "type", (guint)direction,
 +                               "card", card,
 +                               "port-name", port->port,
 +                               "description", port->human_port,
 +                               "origin", gvc_mixer_card_get_name (card),
 +                               "port-available", available,
 +                               "icon-name", port->icon_name,
 +                               NULL);
 +
 +        uidevice = GVC_MIXER_UI_DEVICE (object);
 +        gvc_mixer_ui_device_set_profiles (uidevice, port->profiles);
 +
 +        g_hash_table_insert (is_card_port_an_output (port) ? control->priv->ui_outputs : control->priv->ui_inputs,
 +                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (uidevice)),
 +                             uidevice);
 +
 +
 +        if (available) {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[is_card_port_an_output (port) ? OUTPUT_ADDED : INPUT_ADDED],
 +                               0,
 +                               gvc_mixer_ui_device_get_id (uidevice));
 +        }
 +
 +        g_debug ("update_ui_device_on_port_added, direction %u, description '%s', origin '%s', port available %i",
 +                 direction,
 +                 port->human_port,
 +                 gvc_mixer_card_get_name (card),
 +                 available);
 +}
 +
 +static void
 +update_ui_device_on_port_changed (GvcMixerControl   *control,
 +                                  GvcMixerCardPort  *card_port,
 +                                  pa_card_port_info *new_port_info,
 +                                  GvcMixerCard      *card)
 +{
 +        GList                   *d;
 +        GList                   *devices;
 +        GvcMixerUIDevice        *device;
 +        gboolean                 is_output = is_card_port_an_output (card_port);
 +
 +        devices  = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
 +
 +        for (d = devices; d != NULL; d = d->next) {
 +                GvcMixerCard *device_card;
 +                gchar        *device_port_name;
 +
 +                device = d->data;
 +                g_object_get (G_OBJECT (device),
 +                             "card", &device_card,
 +                             "port-name", &device_port_name,
 +                              NULL);
 +
 +                if (g_strcmp0 (card_port->port, device_port_name) == 0 &&
 +                        device_card == card) {
 +                        gboolean was_available;
 +                        gboolean is_available;
 +                        const GList *card_profiles = gvc_mixer_card_get_profiles (card);
 +
 +                        was_available = card_port->available != PA_PORT_AVAILABLE_NO;
 +                        is_available = new_port_info->available != PA_PORT_AVAILABLE_NO;
 +
 +                        g_debug ("Found the relevant device %s, update its port availability flag to %i, is_output %i",
 +                                 device_port_name,
 +                                 is_available,
 +                                 is_output);
 +
 +                        card_port->available = new_port_info->available;
 +
 +                        g_list_free (card_port->profiles);
 +                        card_port->profiles = determine_profiles_for_port (new_port_info, card_profiles);
 +
 +                        gvc_mixer_ui_device_set_profiles (device, card_port->profiles);
 +
 +                        if (is_available != was_available) {
 +                                g_object_set (G_OBJECT (device),
 +                                              "port-available", is_available, NULL);
 +                                g_signal_emit (G_OBJECT (control),
 +                                               is_output ? signals[is_available ? OUTPUT_ADDED : OUTPUT_REMOVED]
 +                                                         : signals[is_available ? INPUT_ADDED : INPUT_REMOVED],
 +                                               0,
 +                                               gvc_mixer_ui_device_get_id (device));
 +                        }
 +               }
 +               g_free (device_port_name);
 +        }
 +
 +        g_list_free (devices);
 +}
 +
 +static void
 +maybe_remove_ui_device (GvcMixerControl  *control,
 +                        GvcMixerUIDevice *device)
 +{
 +        /* We add UIDevices for ports or for streams, so remove them if the device now
 +         * has neither.
 +         */
 +        if (gvc_mixer_ui_device_get_stream_id (device) == GVC_MIXER_UI_DEVICE_INVALID &&
 +            !gvc_mixer_ui_device_has_ports (device)) {
 +                gboolean is_output = gvc_mixer_ui_device_is_output (device);
 +
 +                g_debug ("Removing UIDevice %s",
 +                         gvc_mixer_ui_device_get_description (device));
 +
 +                g_hash_table_remove (is_output ? control->priv->ui_outputs : control->priv->ui_inputs,
 +                                     GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
 +        }
 +}
 +
 +static void
 +update_ui_device_on_port_removed (GvcMixerControl  *control,
 +                                  GvcMixerCardPort *card_port,
 +                                  GvcMixerCard     *card)
 +{
 +        GList *d;
 +        GList *devices;
 +        gboolean is_output = is_card_port_an_output (card_port);
 +
 +        devices  = g_hash_table_get_values (is_output ? control->priv->ui_outputs : control->priv->ui_inputs);
 +        for (d = devices; d != NULL; d = d->next) {
 +                GvcMixerUIDevice *device = d->data;
 +                GvcMixerCard *device_card;
 +                gchar *device_port_name;
 +
 +                g_object_get (G_OBJECT (device),
 +                             "card", &device_card,
 +                             "port-name", &device_port_name,
 +                              NULL);
 +
 +                if (g_strcmp0 (card_port->port, device_port_name) == 0 && device_card == card) {
 +                        g_object_set (G_OBJECT (device),
 +                                      "card", NULL,
 +                                      "port-name", NULL,
 +                                       NULL);
 +
 +                        g_signal_emit (G_OBJECT (control),
 +                                       signals[is_output ? OUTPUT_REMOVED : INPUT_REMOVED],
 +                                       0,
 +                                       gvc_mixer_ui_device_get_id (device));
 +
 +                        maybe_remove_ui_device (control, device);
 +                }
 +
 +               g_free (device_port_name);
 +        }
 +
 +        g_list_free (devices);
 +}
 +
 +#ifdef HAVE_ALSA
 +typedef struct {
 +        char *port_name_to_set;
 +        guint32 headset_card;
 +} PortStatusData;
 +
 +static void
 +port_status_data_free (PortStatusData *data)
 +{
 +        if (data == NULL)
 +                return;
 +        g_free (data->port_name_to_set);
 +        g_free (data);
 +}
 +
 +/*
 + We need to re-enumerate sources and sinks every time the user makes a choice,
 + because they can change due to use interaction in other software (or policy
 + changes inside PulseAudio). Enumeration means PulseAudio will do a series of
 + callbacks, one for every source/sink.
 + Set the port when we find the correct source/sink.
 + */
 +
 +static void
 +sink_info_cb (pa_context         *c,
 +              const pa_sink_info *i,
 +              int                 eol,
 +              void               *userdata)
 +{
 +        PortStatusData *data = userdata;
 +        pa_operation *o;
 +        guint j;
 +        const char *s;
 +
 +        if (eol != 0) {
 +                port_status_data_free (data);
 +                return;
 +        }
 +
 +        if (i->card != data->headset_card)
 +                return;
 +
 +        s = data->port_name_to_set;
 +
 +        if (i->active_port &&
 +            strcmp (i->active_port->name, s) == 0)
 +                return;
 +
 +        for (j = 0; j < i->n_ports; j++)
 +                if (strcmp (i->ports[j]->name, s) == 0)
 +                        break;
 +
 +        if (j >= i->n_ports)
 +                return;
 +
 +        o = pa_context_set_sink_port_by_index (c, i->index, s, NULL, NULL);
 +        g_clear_pointer (&o, pa_operation_unref);
 +}
 +
 +static void
 +source_info_cb (pa_context           *c,
 +                const pa_source_info *i,
 +                int                   eol,
 +                void                 *userdata)
 +{
 +        PortStatusData *data = userdata;
 +        pa_operation *o;
 +        guint j;
 +        const char *s;
 +
 +        if (eol != 0) {
 +                port_status_data_free (data);
 +                return;
 +        }
 +
 +        if (i->card != data->headset_card)
 +                return;
 +
 +        s = data->port_name_to_set;
 +
 +        for (j = 0; j < i->n_ports; j++) {
 +                if (g_str_equal (i->ports[j]->name, s)) {
 +                        o = pa_context_set_default_source (c,
 +                                                           i->name,
 +                                                           NULL,
 +                                                           NULL);
 +                        if (o == NULL) {
 +                                g_warning ("pa_context_set_default_source() failed");
 +                                return;
 +                        }
 +                }
 +      }
 +
 +        if (i->active_port && strcmp (i->active_port->name, s) == 0)
 +                return;
 +
 +        for (j = 0; j < i->n_ports; j++)
 +                if (strcmp (i->ports[j]->name, s) == 0)
 +                        break;
 +
 +        if (j >= i->n_ports)
 +                return;
 +
 +        o = pa_context_set_source_port_by_index(c, i->index, s, NULL, NULL);
 +        g_clear_pointer (&o, pa_operation_unref);
 +}
 +
 +static void
 +gvc_mixer_control_set_port_status_for_headset (GvcMixerControl *control,
 +                                               guint            id,
 +                                               const char      *port_name,
 +                                               gboolean         is_output)
 +{
 +        pa_operation *o;
 +        PortStatusData *data;
 +
 +        if (port_name == NULL)
 +                return;
 +
 +        data = g_new0 (PortStatusData, 1);
 +        data->port_name_to_set = g_strdup (port_name);
 +        data->headset_card = id;
 +
 +        if (is_output)
 +                o = pa_context_get_sink_info_list (control->priv->pa_context, sink_info_cb, data);
 +        else
 +                o = pa_context_get_source_info_list (control->priv->pa_context, source_info_cb, data);
 +
 +        g_clear_pointer (&o, pa_operation_unref);
 +}
 +#endif /* HAVE_ALSA */
 +
 +static void
 +free_priv_port_names (GvcMixerControl    *control)
 +{
 +#ifdef HAVE_ALSA
 +        g_clear_pointer (&control->priv->headphones_name, g_free);
 +        g_clear_pointer (&control->priv->headsetmic_name, g_free);
 +        g_clear_pointer (&control->priv->headphonemic_name, g_free);
 +        g_clear_pointer (&control->priv->internalspk_name, g_free);
 +        g_clear_pointer (&control->priv->internalmic_name, g_free);
 +#endif
 +}
 +
 +void
 +gvc_mixer_control_set_headset_port (GvcMixerControl      *control,
 +                                    guint                 id,
 +                                    GvcHeadsetPortChoice  choice)
 +{
 +        g_return_if_fail (GVC_IS_MIXER_CONTROL (control));
 +
 +#ifdef HAVE_ALSA
 +        switch (choice) {
 +        case GVC_HEADSET_PORT_CHOICE_HEADPHONES:
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphones_name, TRUE);
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->internalmic_name, FALSE);
 +                break;
 +        case GVC_HEADSET_PORT_CHOICE_HEADSET:
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphones_name, TRUE);
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headsetmic_name, FALSE);
 +                break;
 +        case GVC_HEADSET_PORT_CHOICE_MIC:
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->internalspk_name, TRUE);
 +                gvc_mixer_control_set_port_status_for_headset (control, id, control->priv->headphonemic_name, FALSE);
 +                break;
 +        case GVC_HEADSET_PORT_CHOICE_NONE:
 +        default:
 +                g_assert_not_reached ();
 +        }
 +#else
 +        g_warning ("BUG: libgnome-volume-control compiled without ALSA support");
 +#endif /* HAVE_ALSA */
 +}
 +
 +#ifdef HAVE_ALSA
 +typedef struct {
 +        const pa_card_port_info *headphones;
 +        const pa_card_port_info *headsetmic;
 +        const pa_card_port_info *headphonemic;
 +        const pa_card_port_info *internalmic;
 +        const pa_card_port_info *internalspk;
 +} headset_ports;
 +
 +/*
 +   In PulseAudio without ucm, ports will show up with the following names:
 +   Headphones - analog-output-headphones
 +   Headset mic - analog-input-headset-mic (was: analog-input-microphone-headset)
 +   Jack in mic-in mode - analog-input-headphone-mic (was: analog-input-microphone)
 +
 +   However, since regular mics also show up as analog-input-microphone,
 +   we need to check for certain controls on alsa mixer level too, to know
 +   if we deal with a separate mic jack, or a multi-function jack with a
 +   mic-in mode (also called "headphone mic").
 +   We check for the following names:
 +
 +   Headphone Mic Jack - indicates headphone and mic-in mode share the same jack,
 +     i e, not two separate jacks. Hardware cannot distinguish between a
 +     headphone and a mic.
 +   Headset Mic Phantom Jack - indicates headset jack where hardware can not
 +     distinguish between headphones and headsets
 +   Headset Mic Jack - indicates headset jack where hardware can distinguish
 +     between headphones and headsets. There is no use popping up a dialog in
 +     this case, unless we already need to do this for the mic-in mode.
 +
 +   From the PA_PROCOTOL_VERSION=34, The device_port structure adds 2 members
 +   availability_group and type, with the help of these 2 members, we could
 +   consolidate the port checking and port setting for non-ucm and with-ucm
 +   cases.
 +*/
 +
 +#define HEADSET_PORT_SET(dst, src) \
 +        do { \
 +                if (!(dst) || (dst)->priority < (src)->priority) \
 +                        dst = src; \
 +        } while (0)
 +
 +#define GET_PORT_NAME(x) (x ? g_strdup (x->name) : NULL)
 +
 +static headset_ports *
 +get_headset_ports (GvcMixerControl    *control,
 +                   const pa_card_info *c)
 +{
 +        headset_ports *h;
 +        guint i;
 +
 +        h = g_new0 (headset_ports, 1);
 +
 +        for (i = 0; i < c->n_ports; i++) {
 +                pa_card_port_info *p = c->ports[i];
 +                if (control->priv->server_protocol_version < 34) {
 +                        if (g_str_equal (p->name, "analog-output-headphones"))
 +                                h->headphones = p;
 +                        else if (g_str_equal (p->name, "analog-input-headset-mic"))
 +                                h->headsetmic = p;
 +                        else if (g_str_equal (p->name, "analog-input-headphone-mic"))
 +                                h->headphonemic = p;
 +                        else if (g_str_equal (p->name, "analog-input-internal-mic"))
 +                                h->internalmic = p;
 +                        else if (g_str_equal (p->name, "analog-output-speaker"))
 +                                h->internalspk = p;
 +                } else {
 +#if (PA_PROTOCOL_VERSION >= 34)
 +                        /* in the first loop, set only headphones */
 +                        /* the microphone ports are assigned in the second loop */
 +                        if (p->type == PA_DEVICE_PORT_TYPE_HEADPHONES) {
 +                                if (p->availability_group)
 +                                        HEADSET_PORT_SET (h->headphones, p);
 +                        } else if (p->type == PA_DEVICE_PORT_TYPE_SPEAKER) {
 +                                HEADSET_PORT_SET (h->internalspk, p);
 +                        } else if (p->type == PA_DEVICE_PORT_TYPE_MIC) {
 +                                if (!p->availability_group)
 +                                        HEADSET_PORT_SET (h->internalmic, p);
 +                        }
 +#else
 +                        g_warning_once ("libgnome-volume-control running against PulseAudio %u, "
 +                                        "but compiled against older %d, report a bug to your distribution",
 +                                        control->priv->server_protocol_version,
 +                                        PA_PROTOCOL_VERSION);
 +#endif
 +                }
 +        }
 +
 +#if (PA_PROTOCOL_VERSION >= 34)
 +        if (h->headphones && (control->priv->server_protocol_version >= 34)) {
 +                for (i = 0; i < c->n_ports; i++) {
 +                        pa_card_port_info *p = c->ports[i];
 +                        if (g_strcmp0(h->headphones->availability_group, p->availability_group))
 +                                continue;
 +                        if (p->direction != PA_DIRECTION_INPUT)
 +                                continue;
 +                        if (p->type == PA_DEVICE_PORT_TYPE_HEADSET)
 +                                HEADSET_PORT_SET (h->headsetmic, p);
 +                        else if (p->type == PA_DEVICE_PORT_TYPE_MIC)
 +                                HEADSET_PORT_SET (h->headphonemic, p);
 +                }
 +        }
 +#endif
 +
 +        return h;
 +}
 +
 +static gboolean
 +verify_alsa_card (int       cardindex,
 +                  gboolean *headsetmic,
 +                  gboolean *headphonemic)
 +{
 +        char *ctlstr;
 +        snd_hctl_t *hctl;
 +        snd_ctl_elem_id_t *id;
 +        int err;
 +
 +        *headsetmic = FALSE;
 +        *headphonemic = FALSE;
 +
 +        ctlstr = g_strdup_printf ("hw:%i", cardindex);
 +        if ((err = snd_hctl_open (&hctl, ctlstr, 0)) < 0) {
 +                g_warning ("snd_hctl_open failed: %s", snd_strerror(err));
 +                g_free (ctlstr);
 +                return FALSE;
 +        }
 +        g_free (ctlstr);
 +
 +        if ((err = snd_hctl_load (hctl)) < 0) {
 +                g_warning ("snd_hctl_load failed: %s", snd_strerror(err));
 +                snd_hctl_close (hctl);
 +                return FALSE;
 +        }
 +
 +        snd_ctl_elem_id_alloca (&id);
 +
 +        snd_ctl_elem_id_clear (id);
 +        snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
 +        snd_ctl_elem_id_set_name (id, "Headphone Mic Jack");
 +        if (snd_hctl_find_elem (hctl, id))
 +                *headphonemic = TRUE;
 +
 +        snd_ctl_elem_id_clear (id);
 +        snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
 +        snd_ctl_elem_id_set_name (id, "Headset Mic Phantom Jack");
 +        if (snd_hctl_find_elem (hctl, id))
 +                *headsetmic = TRUE;
 +
 +        if (*headphonemic) {
 +                snd_ctl_elem_id_clear (id);
 +                snd_ctl_elem_id_set_interface (id, SND_CTL_ELEM_IFACE_CARD);
 +                snd_ctl_elem_id_set_name (id, "Headset Mic Jack");
 +                if (snd_hctl_find_elem (hctl, id))
 +                        *headsetmic = TRUE;
 +        }
 +
 +        snd_hctl_close (hctl);
 +        return *headsetmic || *headphonemic;
 +}
 +
 +static void
 +check_audio_device_selection_needed (GvcMixerControl    *control,
 +                                     const pa_card_info *info)
 +{
 +        headset_ports *h;
 +        gboolean start_dialog, stop_dialog;
 +
 +        start_dialog = FALSE;
 +        stop_dialog = FALSE;
 +        h = get_headset_ports (control, info);
 +
 +        if (!h->headphones ||
 +            (!h->headsetmic && !h->headphonemic)) {
 +                /* Not a headset jack */
 +                goto out;
 +        }
 +
 +        if (control->priv->headset_card != (int) info->index) {
 +                int cardindex;
 +                gboolean hsmic = TRUE;
 +                gboolean hpmic = TRUE;
 +                const char *s;
 +
 +                s = pa_proplist_gets (info->proplist, "alsa.card");
 +                if (!s)
 +                        goto out;
 +
 +                cardindex = strtol (s, NULL, 10);
 +                if (cardindex == 0 && strcmp(s, "0") != 0)
 +                        goto out;
 +
 +                if (control->priv->server_protocol_version < 34) {
 +                        if (!verify_alsa_card(cardindex, &hsmic, &hpmic))
 +                                goto out;
 +                }
 +
 +                control->priv->headset_card = info->index;
 +                control->priv->has_headsetmic = hsmic && h->headsetmic;
 +                control->priv->has_headphonemic = hpmic && h->headphonemic;
 +        } else {
 +                start_dialog = (h->headphones->available != PA_PORT_AVAILABLE_NO) && !control->priv->headset_plugged_in;
 +                stop_dialog = (h->headphones->available == PA_PORT_AVAILABLE_NO) && control->priv->headset_plugged_in;
 +        }
 +
 +        control->priv->headset_plugged_in = h->headphones->available != PA_PORT_AVAILABLE_NO;
 +        free_priv_port_names (control);
 +        control->priv->headphones_name = GET_PORT_NAME(h->headphones);
 +        control->priv->headsetmic_name = GET_PORT_NAME(h->headsetmic);
 +        control->priv->headphonemic_name = GET_PORT_NAME(h->headphonemic);
 +        control->priv->internalspk_name = GET_PORT_NAME(h->internalspk);
 +        control->priv->internalmic_name = GET_PORT_NAME(h->internalmic);
 +
 +        if (!start_dialog &&
 +            !stop_dialog)
 +                goto out;
 +
 +        if (stop_dialog) {
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[AUDIO_DEVICE_SELECTION_NEEDED],
 +                               0,
 +                               info->index,
 +                               FALSE,
 +                               GVC_HEADSET_PORT_CHOICE_NONE);
 +        } else {
 +                GvcHeadsetPortChoice choices;
 +
 +                choices = GVC_HEADSET_PORT_CHOICE_HEADPHONES;
 +                if (control->priv->has_headsetmic)
 +                        choices |= GVC_HEADSET_PORT_CHOICE_HEADSET;
 +                if (control->priv->has_headphonemic)
 +                        choices |= GVC_HEADSET_PORT_CHOICE_MIC;
 +
 +                g_signal_emit (G_OBJECT (control),
 +                               signals[AUDIO_DEVICE_SELECTION_NEEDED],
 +                               0,
 +                               info->index,
 +                               TRUE,
 +                               choices);
 +        }
 +
 +out:
 +        g_free (h);
 +}
 +#endif /* HAVE_ALSA */
 +
 +/*
 + * At this point we can determine all devices available to us (besides network 'ports')
 + * This is done by the following:
 + *
 + * - gvc_mixer_card and gvc_mixer_card_ports are created and relevant setters are called.
 + * - If it's a 'normal' card with ports it will create a new ui-device or
 + *   synchronise port availability with the existing device cached for that port on this card. */
 +
 +static void
 +update_card (GvcMixerControl      *control,
 +             const pa_card_info   *info)
 +{
 +        const GList  *m = NULL;
 +        GvcMixerCard *card;
 +        gboolean      is_new = FALSE;
 +        GList *profile_list = NULL;
 +        GList *old_ports;
 +#if 1
 +        guint i;
 +        const char *key;
 +        void *state;
 +
 +        g_debug ("Updating card %s (index: %u driver: %s):",
 +                 info->name, info->index, info->driver);
 +
 +        for (i = 0; i < info->n_profiles; i++) {
 +                struct pa_card_profile_info pi = info->profiles[i];
 +                gboolean is_default;
 +
 +                is_default = (g_strcmp0 (pi.name, info->active_profile->name) == 0);
 +                g_debug ("\tProfile '%s': %d sources %d sinks%s",
 +                         pi.name, pi.n_sources, pi.n_sinks,
 +                         is_default ? " (Current)" : "");
 +        }
 +        state = NULL;
 +        key = pa_proplist_iterate (info->proplist, &state);
 +        while (key != NULL) {
 +                g_debug ("\tProperty: '%s' = '%s'",
 +                        key, pa_proplist_gets (info->proplist, key));
 +                key = pa_proplist_iterate (info->proplist, &state);
 +        }
 +#endif
 +        card = g_hash_table_lookup (control->priv->cards,
 +                                    GUINT_TO_POINTER (info->index));
 +        if (card == NULL) {
 +                card = gvc_mixer_card_new (control->priv->pa_context,
 +                                           info->index);
 +                is_new = TRUE;
 +        }
 +
 +        for (i = 0; i < info->n_profiles; i++) {
 +                GvcMixerCardProfile *profile;
 +                struct pa_card_profile_info pi = info->profiles[i];
 +
 +                profile = g_new0 (GvcMixerCardProfile, 1);
 +                profile->profile = g_strdup (pi.name);
 +                profile->human_profile = g_strdup (pi.description);
 +                profile->status = card_num_streams_to_status (pi.n_sinks, pi.n_sources);
 +                profile->n_sinks = pi.n_sinks;
 +                profile->n_sources = pi.n_sources;
 +                profile->priority = pi.priority;
 +                profile_list = g_list_prepend (profile_list, profile);
 +        }
 +
 +        gvc_mixer_card_set_profiles (card, profile_list);
 +        gvc_mixer_card_set_name (card, pa_proplist_gets (info->proplist, "device.description"));
 +        gvc_mixer_card_set_icon_name (card, pa_proplist_gets (info->proplist, "device.icon_name"));
 +        gvc_mixer_card_set_profile (card, info->active_profile->name);
 +
 +        if (is_new) {
 +                g_hash_table_insert (control->priv->cards,
 +                                     GUINT_TO_POINTER (info->index),
 +                                     card);
 +        }
 +
 +        old_ports = g_list_copy ((GList *)gvc_mixer_card_get_ports (card));
 +        for (m = old_ports; m; m = m->next) {
 +                GvcMixerCardPort *card_port = m->data;
 +                gboolean found = FALSE;
 +
 +                for (i = 0; i < info->n_ports; i++) {
 +                        pa_card_port_info *port = info->ports[i];
 +
 +                        if (g_strcmp0 (card_port->port, port->name) == 0)
 +                                found = TRUE;
 +                }
 +
 +                if (!found) {
 +                        update_ui_device_on_port_removed (control, card_port, card);
 +                        gvc_mixer_card_remove_port (card, card_port);
 +                }
 +        }
 +        g_clear_pointer (&old_ports, g_list_free);
 +
 +        for (i = 0; i < info->n_ports; i++) {
 +                pa_card_port_info *port = info->ports[i];
 +                gboolean found = FALSE;
 +
 +                for (m = gvc_mixer_card_get_ports (card); m; m = m->next) {
 +                        GvcMixerCardPort *card_port = m->data;
 +
 +                        if (g_strcmp0 (card_port->port, port->name) == 0) {
 +                                found = TRUE;
 +                                update_ui_device_on_port_changed (control, card_port, port, card);
 +                        }
 +                }
 +
 +                if (!found) {
 +                        GvcMixerCardPort *card_port;
 +
 +                        card_port = g_new0 (GvcMixerCardPort, 1);
 +                        card_port->port = g_strdup (port->name);
 +                        card_port->human_port = g_strdup (port->description);
 +                        card_port->priority = port->priority;
 +                        card_port->available = port->available;
 +                        card_port->direction = port->direction;
 +                        card_port->icon_name = g_strdup (pa_proplist_gets (port->proplist, "device.icon_name"));
 +                        card_port->profiles = determine_profiles_for_port (port, profile_list);
 +
 +                        gvc_mixer_card_add_port (card, card_port);
 +                        update_ui_device_on_port_added (control, card_port, card);
 +                }
 +        }
 +
 +#ifdef HAVE_ALSA
 +        check_audio_device_selection_needed (control, info);
 +#endif /* HAVE_ALSA */
 +
 +        g_signal_emit (G_OBJECT (control),
 +                       signals[CARD_ADDED],
 +                       0,
 +                       info->index);
 +}
 +
 +static void
 +_pa_context_get_sink_info_cb (pa_context         *context,
 +                              const pa_sink_info *i,
 +                              int                 eol,
 +                              void               *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 +                        return;
 +                }
 +
 +                g_warning ("Sink callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_sink (control, i);
 +}
 +
 +static void
 +_pa_context_get_source_info_cb (pa_context           *context,
 +                                const pa_source_info *i,
 +                                int                   eol,
 +                                void                 *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 +                        return;
 +                }
 +
 +                g_warning ("Source callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_source (control, i);
 +}
 +
 +static void
 +_pa_context_get_sink_input_info_cb (pa_context               *context,
 +                                    const pa_sink_input_info *i,
 +                                    int                       eol,
 +                                    void                     *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 +                        return;
 +                }
 +
 +                g_warning ("Sink input callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_sink_input (control, i);
 +}
 +
 +static void
 +_pa_context_get_source_output_info_cb (pa_context                  *context,
 +                                       const pa_source_output_info *i,
 +                                       int                          eol,
 +                                       void                        *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 +                        return;
 +                }
 +
 +                g_warning ("Source output callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0)  {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_source_output (control, i);
 +}
 +
 +static void
 +_pa_context_get_client_info_cb (pa_context           *context,
 +                                const pa_client_info *i,
 +                                int                   eol,
 +                                void                 *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY) {
 +                        return;
 +                }
 +
 +                g_warning ("Client callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_client (control, i);
 +}
 +
 +static void
 +_pa_context_get_card_info_by_index_cb (pa_context *context,
 +                                       const pa_card_info *i,
 +                                       int eol,
 +                                       void *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                if (pa_context_errno (context) == PA_ERR_NOENTITY)
 +                        return;
 +
 +                g_warning ("Card callback failure");
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                return;
 +        }
 +
 +        update_card (control, i);
 +}
 +
 +static void
 +_pa_context_get_server_info_cb (pa_context           *context,
 +                                const pa_server_info *i,
 +                                void                 *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (i == NULL) {
 +                g_warning ("Server info callback failure");
 +                return;
 +        }
 +        g_debug ("get server info");
 +        update_server (control, i);
 +        dec_outstanding (control);
 +}
 +
 +static void
 +remove_event_role_stream (GvcMixerControl *control)
 +{
 +        g_debug ("Removing event role");
 +}
 +
 +static void
 +update_event_role_stream (GvcMixerControl                  *control,
 +                          const pa_ext_stream_restore_info *info)
 +{
 +        GvcMixerStream *stream;
 +        gboolean        is_new;
 +        pa_volume_t     max_volume;
 +
 +        if (strcmp (info->name, "sink-input-by-media-role:event") != 0) {
 +                return;
 +        }
 +
 +#if 0
 +        g_debug ("Updating event role: name='%s' device='%s'",
 +                 info->name,
 +                 info->device);
 +#endif
 +
 +        is_new = FALSE;
 +
 +        if (!control->priv->event_sink_input_is_set) {
 +                pa_channel_map pa_map;
 +                GvcChannelMap *map;
 +
 +                pa_map.channels = 1;
 +                pa_map.map[0] = PA_CHANNEL_POSITION_MONO;
 +                map = gvc_channel_map_new_from_pa_channel_map (&pa_map);
 +
 +                stream = gvc_mixer_event_role_new (control->priv->pa_context,
 +                                                   info->device,
 +                                                   map);
 +                control->priv->event_sink_input_id = gvc_mixer_stream_get_id (stream);
 +                control->priv->event_sink_input_is_set = TRUE;
 +
 +                is_new = TRUE;
 +        } else {
 +                stream = g_hash_table_lookup (control->priv->all_streams,
 +                                              GUINT_TO_POINTER (control->priv->event_sink_input_id));
 +        }
 +
 +        /* 0 channels here means there is no valid volume in
 +         * pa_ext_stream_restore_info() and in the saved stream entry of
 +         * module-stream-restore in PulseAudio. */
 +        if (info->volume.channels == 0)
 +                max_volume = PA_VOLUME_NORM;
 +        else
 +                max_volume = pa_cvolume_max (&info->volume);
 +
 +        gvc_mixer_stream_set_name (stream, _("System Sounds"));
 +        gvc_mixer_stream_set_icon_name (stream, "audio-x-generic");
 +        gvc_mixer_stream_set_volume (stream, (guint)max_volume);
 +        gvc_mixer_stream_set_is_muted (stream, info->mute);
 +
 +        if (is_new) {
 +                add_stream (control, stream);
 +        }
 +}
 +
 +static void
 +_pa_ext_stream_restore_read_cb (pa_context                       *context,
 +                                const pa_ext_stream_restore_info *i,
 +                                int                               eol,
 +                                void                             *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        if (eol < 0) {
 +                g_debug ("Failed to initialized stream_restore extension: %s",
 +                         pa_strerror (pa_context_errno (context)));
 +                remove_event_role_stream (control);
 +                return;
 +        }
 +
 +        if (eol > 0) {
 +                dec_outstanding (control);
 +                /* If we don't have an event stream to restore, then
 +                 * set one up with a default 100% volume */
 +                if (!control->priv->event_sink_input_is_set) {
 +                        pa_ext_stream_restore_info info;
 +
 +                        memset (&info, 0, sizeof(info));
 +                        info.name = "sink-input-by-media-role:event";
 +                        info.volume.channels = 1;
 +                        info.volume.values[0] = PA_VOLUME_NORM;
 +                        update_event_role_stream (control, &info);
 +                }
 +                return;
 +        }
 +
 +        update_event_role_stream (control, i);
 +}
 +
 +static void
 +_pa_ext_stream_restore_subscribe_cb (pa_context *context,
 +                                     void       *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +        pa_operation    *o;
 +
 +        o = pa_ext_stream_restore_read (context,
 +                                        _pa_ext_stream_restore_read_cb,
 +                                        control);
 +        if (o == NULL) {
 +                g_warning ("pa_ext_stream_restore_read() failed");
 +                return;
 +        }
 +
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_server_info (GvcMixerControl *control,
 +                        int              index)
 +{
 +        pa_operation *o;
 +
 +        o = pa_context_get_server_info (control->priv->pa_context,
 +                                        _pa_context_get_server_info_cb,
 +                                        control);
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_server_info() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_client_info (GvcMixerControl *control,
 +                        int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_client_info_list (control->priv->pa_context,
 +                                                     _pa_context_get_client_info_cb,
 +                                                     control);
 +        } else {
 +                o = pa_context_get_client_info (control->priv->pa_context,
 +                                                index,
 +                                                _pa_context_get_client_info_cb,
 +                                                control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_client_info_list() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_card (GvcMixerControl *control,
 +                 int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_card_info_list (control->priv->pa_context,
 +                                                   _pa_context_get_card_info_by_index_cb,
 +                                                   control);
 +        } else {
 +                o = pa_context_get_card_info_by_index (control->priv->pa_context,
 +                                                       index,
 +                                                       _pa_context_get_card_info_by_index_cb,
 +                                                       control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_card_info_by_index() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_sink_info (GvcMixerControl *control,
 +                      int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_sink_info_list (control->priv->pa_context,
 +                                                   _pa_context_get_sink_info_cb,
 +                                                   control);
 +        } else {
 +                o = pa_context_get_sink_info_by_index (control->priv->pa_context,
 +                                                       index,
 +                                                       _pa_context_get_sink_info_cb,
 +                                                       control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_sink_info_list() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_source_info (GvcMixerControl *control,
 +                        int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_source_info_list (control->priv->pa_context,
 +                                                     _pa_context_get_source_info_cb,
 +                                                     control);
 +        } else {
 +                o = pa_context_get_source_info_by_index(control->priv->pa_context,
 +                                                        index,
 +                                                        _pa_context_get_source_info_cb,
 +                                                        control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_source_info_list() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_sink_input_info (GvcMixerControl *control,
 +                            int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_sink_input_info_list (control->priv->pa_context,
 +                                                         _pa_context_get_sink_input_info_cb,
 +                                                         control);
 +        } else {
 +                o = pa_context_get_sink_input_info (control->priv->pa_context,
 +                                                    index,
 +                                                    _pa_context_get_sink_input_info_cb,
 +                                                    control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_sink_input_info_list() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +req_update_source_output_info (GvcMixerControl *control,
 +                               int              index)
 +{
 +        pa_operation *o;
 +
 +        if (index < 0) {
 +                o = pa_context_get_source_output_info_list (control->priv->pa_context,
 +                                                            _pa_context_get_source_output_info_cb,
 +                                                            control);
 +        } else {
 +                o = pa_context_get_source_output_info (control->priv->pa_context,
 +                                                       index,
 +                                                       _pa_context_get_source_output_info_cb,
 +                                                       control);
 +        }
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_get_source_output_info_list() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +}
 +
 +static void
 +remove_client (GvcMixerControl *control,
 +               guint            index)
 +{
 +        g_hash_table_remove (control->priv->clients,
 +                             GUINT_TO_POINTER (index));
 +}
 +
 +static void
 +remove_card (GvcMixerControl *control,
 +             guint            index)
 +{
 +
 +        GList *devices, *d;
 +
 +        devices = g_list_concat (g_hash_table_get_values (control->priv->ui_inputs),
 +                                 g_hash_table_get_values (control->priv->ui_outputs));
 +
 +        for (d = devices; d != NULL; d = d->next) {
 +                GvcMixerCard *card;
 +                GvcMixerUIDevice *device = d->data;
 +
 +                g_object_get (G_OBJECT (device), "card", &card, NULL);
 +
 +                if (card == NULL)
 +                        continue;
 +
 +                if (gvc_mixer_card_get_index (card) == index) {
 +                        g_signal_emit (G_OBJECT (control),
 +                                       signals[gvc_mixer_ui_device_is_output (device) ? OUTPUT_REMOVED : INPUT_REMOVED],
 +                                       0,
 +                                       gvc_mixer_ui_device_get_id (device));
 +                        g_debug ("Card removal remove device %s",
 +                                 gvc_mixer_ui_device_get_description (device));
 +                        g_hash_table_remove (gvc_mixer_ui_device_is_output (device) ? control->priv->ui_outputs : control->priv->ui_inputs,
 +                                             GUINT_TO_POINTER (gvc_mixer_ui_device_get_id (device)));
 +                }
 +        }
 +
 +        g_list_free (devices);
 +
 +        g_hash_table_remove (control->priv->cards,
 +                             GUINT_TO_POINTER (index));
 +
 +        g_signal_emit (G_OBJECT (control),
 +                       signals[CARD_REMOVED],
 +                       0,
 +                       index);
 +}
 +
 +static void
 +remove_sink (GvcMixerControl *control,
 +             guint            index)
 +{
 +        GvcMixerStream   *stream;
 +        GvcMixerUIDevice *device;
 +
 +        g_debug ("Removing sink: index=%u", index);
 +
 +        stream = g_hash_table_lookup (control->priv->sinks,
 +                                      GUINT_TO_POINTER (index));
 +        if (stream == NULL)
 +                return;
 +
 +        device = gvc_mixer_control_lookup_device_from_stream (control, stream);
 +
 +        if (device != NULL) {
 +                gvc_mixer_ui_device_invalidate_stream (device);
 +                if (!gvc_mixer_ui_device_has_ports (device)) {
 +                        g_signal_emit (G_OBJECT (control),
 +                                       signals[OUTPUT_REMOVED],
 +                                       0,
 +                                       gvc_mixer_ui_device_get_id (device));
 +                } else {
 +                        GList *devices, *d;
 +
 +                        devices = g_hash_table_get_values (control->priv->ui_outputs);
 +
 +                        for (d = devices; d != NULL; d = d->next) {
 +                                guint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
 +                                device = d->data;
 +                                g_object_get (G_OBJECT (device),
 +                                             "stream-id", &stream_id,
 +                                              NULL);
 +                                if (stream_id == gvc_mixer_stream_get_id (stream))
 +                                        gvc_mixer_ui_device_invalidate_stream (device);
 +                        }
 +
 +                        g_list_free (devices);
 +                }
 +        }
 +
 +        g_hash_table_remove (control->priv->sinks,
 +                             GUINT_TO_POINTER (index));
 +
 +        remove_stream (control, stream);
 +}
 +
 +static void
 +remove_source (GvcMixerControl *control,
 +               guint            index)
 +{
 +        GvcMixerStream   *stream;
 +        GvcMixerUIDevice *device;
 +
 +        g_debug ("Removing source: index=%u", index);
 +
 +        stream = g_hash_table_lookup (control->priv->sources,
 +                                      GUINT_TO_POINTER (index));
 +        if (stream == NULL)
 +                return;
 +
 +        device = gvc_mixer_control_lookup_device_from_stream (control, stream);
 +
 +        if (device != NULL) {
 +                gvc_mixer_ui_device_invalidate_stream (device);
 +                if (!gvc_mixer_ui_device_has_ports (device)) {
 +                        g_signal_emit (G_OBJECT (control),
 +                                       signals[INPUT_REMOVED],
 +                                       0,
 +                                       gvc_mixer_ui_device_get_id (device));
 +                } else {
 +                        GList *devices, *d;
 +
 +                        devices = g_hash_table_get_values (control->priv->ui_inputs);
 +
 +                        for (d = devices; d != NULL; d = d->next) {
 +                                guint stream_id = GVC_MIXER_UI_DEVICE_INVALID;
 +                                device = d->data;
 +                                g_object_get (G_OBJECT (device),
 +                                             "stream-id", &stream_id,
 +                                              NULL);
 +                                if (stream_id == gvc_mixer_stream_get_id (stream))
 +                                        gvc_mixer_ui_device_invalidate_stream (device);
 +                        }
 +
 +                        g_list_free (devices);
 +                }
 +        }
 +
 +        g_hash_table_remove (control->priv->sources,
 +                             GUINT_TO_POINTER (index));
 +
 +        remove_stream (control, stream);
 +}
 +
 +static void
 +remove_sink_input (GvcMixerControl *control,
 +                   guint            index)
 +{
 +        GvcMixerStream *stream;
 +
 +        g_debug ("Removing sink input: index=%u", index);
 +
 +        stream = g_hash_table_lookup (control->priv->sink_inputs,
 +                                      GUINT_TO_POINTER (index));
 +        if (stream == NULL) {
 +                return;
 +        }
 +        g_hash_table_remove (control->priv->sink_inputs,
 +                             GUINT_TO_POINTER (index));
 +
 +        remove_stream (control, stream);
 +}
 +
 +static void
 +remove_source_output (GvcMixerControl *control,
 +                      guint            index)
 +{
 +        GvcMixerStream *stream;
 +
 +        g_debug ("Removing source output: index=%u", index);
 +
 +        stream = g_hash_table_lookup (control->priv->source_outputs,
 +                                      GUINT_TO_POINTER (index));
 +        if (stream == NULL) {
 +                return;
 +        }
 +        g_hash_table_remove (control->priv->source_outputs,
 +                             GUINT_TO_POINTER (index));
 +
 +        remove_stream (control, stream);
 +}
 +
 +static void
 +_pa_context_subscribe_cb (pa_context                  *context,
 +                          pa_subscription_event_type_t t,
 +                          uint32_t                     index,
 +                          void                        *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
 +        case PA_SUBSCRIPTION_EVENT_SINK:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_sink (control, index);
 +                } else {
 +                        req_update_sink_info (control, index);
 +                }
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_SOURCE:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_source (control, index);
 +                } else {
 +                        req_update_source_info (control, index);
 +                }
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_sink_input (control, index);
 +                } else {
 +                        req_update_sink_input_info (control, index);
 +                }
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_source_output (control, index);
 +                } else {
 +                        req_update_source_output_info (control, index);
 +                }
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_CLIENT:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_client (control, index);
 +                } else {
 +                        req_update_client_info (control, index);
 +                }
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_SERVER:
 +                req_update_server_info (control, index);
 +                break;
 +
 +        case PA_SUBSCRIPTION_EVENT_CARD:
 +                if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
 +                        remove_card (control, index);
 +                } else {
 +                        req_update_card (control, index);
 +                }
 +                break;
 +        default:
 +                break;
 +        }
 +}
 +
 +static void
 +gvc_mixer_control_ready (GvcMixerControl *control)
 +{
 +        pa_operation *o;
 +
 +        pa_context_set_subscribe_callback (control->priv->pa_context,
 +                                           _pa_context_subscribe_cb,
 +                                           control);
 +        o = pa_context_subscribe (control->priv->pa_context,
 +                                  (pa_subscription_mask_t)
 +                                  (PA_SUBSCRIPTION_MASK_SINK|
 +                                   PA_SUBSCRIPTION_MASK_SOURCE|
 +                                   PA_SUBSCRIPTION_MASK_SINK_INPUT|
 +                                   PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
 +                                   PA_SUBSCRIPTION_MASK_CLIENT|
 +                                   PA_SUBSCRIPTION_MASK_SERVER|
 +                                   PA_SUBSCRIPTION_MASK_CARD),
 +                                  NULL,
 +                                  NULL);
 +
 +        if (o == NULL) {
 +                g_warning ("pa_context_subscribe() failed");
 +                return;
 +        }
 +        pa_operation_unref (o);
 +
 +        req_update_server_info (control, -1);
 +        req_update_card (control, -1);
 +        req_update_client_info (control, -1);
 +        req_update_sink_info (control, -1);
 +        req_update_source_info (control, -1);
 +        req_update_sink_input_info (control, -1);
 +        req_update_source_output_info (control, -1);
 +
 +        control->priv->server_protocol_version = pa_context_get_server_protocol_version (control->priv->pa_context);
 +
 +        control->priv->n_outstanding = 6;
 +
 +        /* This call is not always supported */
 +        o = pa_ext_stream_restore_read (control->priv->pa_context,
 +                                        _pa_ext_stream_restore_read_cb,
 +                                        control);
 +        if (o != NULL) {
 +                pa_operation_unref (o);
 +                control->priv->n_outstanding++;
 +
 +                pa_ext_stream_restore_set_subscribe_cb (control->priv->pa_context,
 +                                                        _pa_ext_stream_restore_subscribe_cb,
 +                                                        control);
 +
 +                o = pa_ext_stream_restore_subscribe (control->priv->pa_context,
 +                                                     1,
 +                                                     NULL,
 +                                                     NULL);
 +                if (o != NULL) {
 +                        pa_operation_unref (o);
 +                }
 +
 +        } else {
 +                g_debug ("Failed to initialized stream_restore extension: %s",
 +                         pa_strerror (pa_context_errno (control->priv->pa_context)));
 +        }
 +}
 +
 +static void
 +gvc_mixer_new_pa_context (GvcMixerControl *self)
 +{
 +        pa_proplist     *proplist;
 +
 +        g_return_if_fail (self);
 +        g_return_if_fail (!self->priv->pa_context);
 +
 +        proplist = pa_proplist_new ();
 +        pa_proplist_sets (proplist,
 +                          PA_PROP_APPLICATION_NAME,
 +                          self->priv->name);
 +        pa_proplist_sets (proplist,
 +                          PA_PROP_APPLICATION_ID,
 +                          "org.gnome.VolumeControl");
 +        pa_proplist_sets (proplist,
 +                          PA_PROP_APPLICATION_ICON_NAME,
 +                          "multimedia-volume-control");
 +        pa_proplist_sets (proplist,
 +                          PA_PROP_APPLICATION_VERSION,
 +                          PACKAGE_VERSION);
 +
 +        self->priv->pa_context = pa_context_new_with_proplist (self->priv->pa_api, NULL, proplist);
 +
 +        pa_proplist_free (proplist);
 +        g_assert (self->priv->pa_context);
 +}
 +
 +static void
 +remove_all_items (GvcMixerControl *control,
 +                  GHashTable *hash_table,
 +                  void (*remove_item)(GvcMixerControl *control, guint index))
 +{
 +        GHashTableIter iter;
 +        gpointer key, value;
 +
 +        g_hash_table_iter_init (&iter, hash_table);
 +        while (g_hash_table_iter_next (&iter, &key, &value)) {
 +                if (remove_item) {
 +                        remove_item (control, GPOINTER_TO_UINT (key));
 +                        g_hash_table_remove (hash_table, key);
 +                        g_hash_table_iter_init (&iter, hash_table);
 +                } else {
 +                        g_hash_table_iter_remove (&iter);
 +                }
 +        }
 +}
 +
 +static gboolean
 +idle_reconnect (gpointer data)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (data);
 +
 +        g_return_val_if_fail (control, FALSE);
 +
 +        g_debug ("Reconnect: clean up all objects");
 +
 +        remove_all_items (control, control->priv->sinks, remove_sink);
 +        remove_all_items (control, control->priv->sources, remove_source);
 +        remove_all_items (control, control->priv->sink_inputs, remove_sink_input);
 +        remove_all_items (control, control->priv->source_outputs, remove_source_output);
 +        remove_all_items (control, control->priv->cards, remove_card);
 +        remove_all_items (control, control->priv->ui_inputs, NULL);
 +        remove_all_items (control, control->priv->ui_outputs, NULL);
 +        remove_all_items (control, control->priv->clients, remove_client);
 +
 +        g_debug ("Reconnect: make new connection");
 +
 +        if (control->priv->pa_context) {
 +                pa_context_unref (control->priv->pa_context);
 +                control->priv->pa_context = NULL;
 +                control->priv->server_protocol_version = 0;
 +                gvc_mixer_new_pa_context (control);
 +        }
 +
 +        gvc_mixer_control_open (control); /* cannot fail */
 +
 +        control->priv->reconnect_id = 0;
 +        return FALSE;
 +}
 +
 +static void
 +_pa_context_state_cb (pa_context *context,
 +                      void       *userdata)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (userdata);
 +
 +        switch (pa_context_get_state (context)) {
 +        case PA_CONTEXT_UNCONNECTED:
 +        case PA_CONTEXT_CONNECTING:
 +        case PA_CONTEXT_AUTHORIZING:
 +        case PA_CONTEXT_SETTING_NAME:
 +                break;
 +
 +        case PA_CONTEXT_READY:
 +                gvc_mixer_control_ready (control);
 +                break;
 +
 +        case PA_CONTEXT_FAILED:
 +                control->priv->state = GVC_STATE_FAILED;
 +                g_signal_emit (control, signals[STATE_CHANGED], 0, GVC_STATE_FAILED);
 +                if (control->priv->reconnect_id == 0)
 +                        control->priv->reconnect_id = g_timeout_add_seconds (RECONNECT_DELAY, idle_reconnect, control);
 +                break;
 +
 +        case PA_CONTEXT_TERMINATED:
 +        default:
 +                /* FIXME: */
 +                break;
 +        }
 +}
 +
 +gboolean
 +gvc_mixer_control_open (GvcMixerControl *control)
 +{
 +        int res;
 +
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 +        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
 +        g_return_val_if_fail (pa_context_get_state (control->priv->pa_context) == PA_CONTEXT_UNCONNECTED, FALSE);
 +
 +        pa_context_set_state_callback (control->priv->pa_context,
 +                                       _pa_context_state_cb,
 +                                       control);
 +
 +        control->priv->state = GVC_STATE_CONNECTING;
 +        g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CONNECTING);
 +        res = pa_context_connect (control->priv->pa_context, NULL, (pa_context_flags_t) PA_CONTEXT_NOFAIL, NULL);
 +        if (res < 0) {
 +                g_warning ("Failed to connect context: %s",
 +                           pa_strerror (pa_context_errno (control->priv->pa_context)));
 +        }
 +
 +        return res;
 +}
 +
 +gboolean
 +gvc_mixer_control_close (GvcMixerControl *control)
 +{
 +        g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), FALSE);
 +        g_return_val_if_fail (control->priv->pa_context != NULL, FALSE);
 +
 +        pa_context_disconnect (control->priv->pa_context);
 +
 +        control->priv->state = GVC_STATE_CLOSED;
 +        g_signal_emit (G_OBJECT (control), signals[STATE_CHANGED], 0, GVC_STATE_CLOSED);
 +        return TRUE;
 +}
 +
 +static void
 +gvc_mixer_control_dispose (GObject *object)
 +{
 +        GvcMixerControl *control = GVC_MIXER_CONTROL (object);
 +
 +        if (control->priv->reconnect_id != 0) {
 +                g_source_remove (control->priv->reconnect_id);
 +                control->priv->reconnect_id = 0;
 +        }
 +
 +        if (control->priv->pa_context != NULL) {
 +                pa_context_unref (control->priv->pa_context);
 +                control->priv->pa_context = NULL;
 +        }
 +
 +        if (control->priv->default_source_name != NULL) {
 +                g_free (control->priv->default_source_name);
 +                control->priv->default_source_name = NULL;
 +        }
 +        if (control->priv->default_sink_name != NULL) {
 +                g_free (control->priv->default_sink_name);
 +                control->priv->default_sink_name = NULL;
 +        }
 +
 +        if (control->priv->pa_mainloop != NULL) {
 +                pa_glib_mainloop_free (control->priv->pa_mainloop);
 +                control->priv->pa_mainloop = NULL;
 +        }
 +
 +        if (control->priv->all_streams != NULL) {
 +                g_hash_table_destroy (control->priv->all_streams);
 +                control->priv->all_streams = NULL;
 +        }
 +
 +        if (control->priv->sinks != NULL) {
 +                g_hash_table_destroy (control->priv->sinks);
 +                control->priv->sinks = NULL;
 +        }
 +        if (control->priv->sources != NULL) {
 +                g_hash_table_destroy (control->priv->sources);
 +                control->priv->sources = NULL;
 +        }
 +        if (control->priv->sink_inputs != NULL) {
 +                g_hash_table_destroy (control->priv->sink_inputs);
 +                control->priv->sink_inputs = NULL;
 +        }
 +        if (control->priv->source_outputs != NULL) {
 +                g_hash_table_destroy (control->priv->source_outputs);
 +                control->priv->source_outputs = NULL;
 +        }
 +        if (control->priv->clients != NULL) {
 +                g_hash_table_destroy (control->priv->clients);
 +                control->priv->clients = NULL;
 +        }
 +        if (control->priv->cards != NULL) {
 +                g_hash_table_destroy (control->priv->cards);
 +                control->priv->cards = NULL;
 +        }
 +        if (control->priv->ui_outputs != NULL) {
 +                g_hash_table_destroy (control->priv->ui_outputs);
 +                control->priv->ui_outputs = NULL;
 +        }
 +        if (control->priv->ui_inputs != NULL) {
 +                g_hash_table_destroy (control->priv->ui_inputs);
 +                control->priv->ui_inputs = NULL;
 +        }
 +
 +        free_priv_port_names (control);
 +        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->dispose (object);
 +}
 +
 +static void
 +gvc_mixer_control_set_property (GObject       *object,
 +                                guint          prop_id,
 +                                const GValue  *value,
 +                                GParamSpec    *pspec)
 +{
 +        GvcMixerControl *self = GVC_MIXER_CONTROL (object);
 +
 +        switch (prop_id) {
 +        case PROP_NAME:
 +                g_free (self->priv->name);
 +                self->priv->name = g_value_dup_string (value);
 +                g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_NAME]);
 +                break;
 +        default:
 +                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 +                break;
 +        }
 +}
 +
 +static void
 +gvc_mixer_control_get_property (GObject     *object,
 +                                guint        prop_id,
 +                                GValue      *value,
 +                                GParamSpec  *pspec)
 +{
 +        GvcMixerControl *self = GVC_MIXER_CONTROL (object);
 +
 +        switch (prop_id) {
 +        case PROP_NAME:
 +                g_value_set_string (value, self->priv->name);
 +                break;
 +        default:
 +                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 +                break;
 +        }
 +}
 +
 +
 +static GObject *
 +gvc_mixer_control_constructor (GType                  type,
 +                               guint                  n_construct_properties,
 +                               GObjectConstructParam *construct_params)
 +{
 +        GObject         *object;
 +        GvcMixerControl *self;
 +
 +        object = G_OBJECT_CLASS (gvc_mixer_control_parent_class)->constructor (type, n_construct_properties, construct_params);
 +
 +        self = GVC_MIXER_CONTROL (object);
 +
 +        gvc_mixer_new_pa_context (self);
 +        self->priv->profile_swapping_device_id = GVC_MIXER_UI_DEVICE_INVALID;
 +
 +        return object;
 +}
 +
 +static void
 +gvc_mixer_control_class_init (GvcMixerControlClass *klass)
 +{
 +        GObjectClass   *object_class = G_OBJECT_CLASS (klass);
 +
 +        object_class->constructor = gvc_mixer_control_constructor;
 +        object_class->dispose = gvc_mixer_control_dispose;
 +        object_class->finalize = gvc_mixer_control_finalize;
 +        object_class->set_property = gvc_mixer_control_set_property;
 +        object_class->get_property = gvc_mixer_control_get_property;
 +
 +        obj_props[PROP_NAME] = g_param_spec_string ("name",
 +                                                    "Name",
 +                                                    "Name to display for this mixer control",
 +                                                    NULL,
 +                                                    G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS);
 +        g_object_class_install_properties (object_class, N_PROPS, obj_props);
 +
 +        signals [STATE_CHANGED] =
 +                g_signal_new ("state-changed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, state_changed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [STREAM_ADDED] =
 +                g_signal_new ("stream-added",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_added),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [STREAM_REMOVED] =
 +                g_signal_new ("stream-removed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_removed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [STREAM_CHANGED] =
 +                g_signal_new ("stream-changed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, stream_changed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [AUDIO_DEVICE_SELECTION_NEEDED] =
 +                g_signal_new ("audio-device-selection-needed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              0,
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_BOOLEAN, G_TYPE_UINT);
 +        signals [CARD_ADDED] =
 +                g_signal_new ("card-added",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, card_added),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [CARD_REMOVED] =
 +                g_signal_new ("card-removed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, card_removed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [DEFAULT_SINK_CHANGED] =
 +                g_signal_new ("default-sink-changed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, default_sink_changed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [DEFAULT_SOURCE_CHANGED] =
 +                g_signal_new ("default-source-changed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, default_source_changed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [ACTIVE_OUTPUT_UPDATE] =
 +                g_signal_new ("active-output-update",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, active_output_update),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [ACTIVE_INPUT_UPDATE] =
 +                g_signal_new ("active-input-update",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, active_input_update),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [OUTPUT_ADDED] =
 +                g_signal_new ("output-added",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, output_added),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [INPUT_ADDED] =
 +                g_signal_new ("input-added",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, input_added),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [OUTPUT_REMOVED] =
 +                g_signal_new ("output-removed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, output_removed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +        signals [INPUT_REMOVED] =
 +                g_signal_new ("input-removed",
 +                              G_TYPE_FROM_CLASS (klass),
 +                              G_SIGNAL_RUN_LAST,
 +                              G_STRUCT_OFFSET (GvcMixerControlClass, input_removed),
 +                              NULL, NULL, NULL,
 +                              G_TYPE_NONE, 1, G_TYPE_UINT);
 +}
 +
 +
 +static void
 +gvc_mixer_control_init (GvcMixerControl *control)
 +{
 +        control->priv = gvc_mixer_control_get_instance_private (control);
 +
 +        control->priv->pa_mainloop = pa_glib_mainloop_new (g_main_context_default ());
 +        g_assert (control->priv->pa_mainloop);
 +
 +        control->priv->pa_api = pa_glib_mainloop_get_api (control->priv->pa_mainloop);
 +        g_assert (control->priv->pa_api);
 +
 +        control->priv->all_streams = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->sinks = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->sources = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->sink_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->source_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->cards = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->ui_outputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +        control->priv->ui_inputs = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_object_unref);
 +
 +        control->priv->clients = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_free);
 +
 +#ifdef HAVE_ALSA
 +        control->priv->headset_card = -1;
 +#endif /* HAVE_ALSA */
 +
 +        control->priv->state = GVC_STATE_CLOSED;
 +}
 +
 +static void
 +gvc_mixer_control_finalize (GObject *object)
 +{
 +        GvcMixerControl *mixer_control;
 +
 +        g_return_if_fail (object != NULL);
 +        g_return_if_fail (GVC_IS_MIXER_CONTROL (object));
 +
 +        mixer_control = GVC_MIXER_CONTROL (object);
 +        g_free (mixer_control->priv->name);
 +        mixer_control->priv->name = NULL;
 +
 +        g_return_if_fail (mixer_control->priv != NULL);
 +        G_OBJECT_CLASS (gvc_mixer_control_parent_class)->finalize (object);
 +}
 +
 +GvcMixerControl *
 +gvc_mixer_control_new (const char *name)
 +{
 +        GObject *control;
 +        control = g_object_new (GVC_TYPE_MIXER_CONTROL,
 +                                "name", name,
 +                                NULL);
 +        return GVC_MIXER_CONTROL (control);
 +}
 +
 +gdouble
 +gvc_mixer_control_get_vol_max_norm (GvcMixerControl *control)
 +{
 +      g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), 0);
 +      return (gdouble) PA_VOLUME_NORM;
 +}
 +
 +gdouble
 +gvc_mixer_control_get_vol_max_amplified (GvcMixerControl *control)
 +{
 +      g_return_val_if_fail (GVC_IS_MIXER_CONTROL (control), 0);
 +      return (gdouble) PA_VOLUME_UI_MAX;
 +}